C++ multithreading learning 05 timeout lock, recursive lock and shared lock

1. Timed_mutex timed lock

Function: Avoid long-term deadlock, can record lock acquisition, multiple timeouts, can record logs, and obtain error conditions

In 04, because try_lock() will not block the thread but always occupies CPU resources, add sleep_for (100ms) to delay and block the thread for a while to give other threads a chance, but this delay is called this_thread The following function:

if (!mux.try_lock())
        {
    
    
            cout << "." << flush;
            this_thread::sleep_for(100ms);
            continue;
        }

You can also use the delay as a parameter of the lock constructor, using the timed_mutex:

timed_mutex tmux;

void ThreadMainTime(int i)
{
    
    


    for (;;)
    {
    
    
        if (!tmux.try_lock_for(chrono::milliseconds(500)))
        {
    
     
            //如果未在规定时间内拿到锁,那么这段代码可能会出现问题,这里可以进行日志的写入,便于调试
            cout << i << "[try_lock_for timeout]" << endl;
            continue;
        }
        cout << i << "[in]" << endl;
        this_thread::sleep_for(2000ms);
        tmux.unlock();
        this_thread::sleep_for(1ms);
    }
}

Similarly, in order to ensure that unlock can release resources, finally delay:

Three threads are created, and each thread is blocked for 500ms after trying to unlock.
Question: Why are the three threads detach from each other and the printing is not messy?

int main(int argc, char* argv[])
{
    
    
 
    for (int i = 0; i < 3; i++)
    {
    
    
        thread th(ThreadMainTime, i + 1);
        th.detach();
    }
    getchar();
  }

[in] indicates that the thread has entered
[try_lock_for timeout] indicates that the entry failed
insert image description here

Two, recursive lock (reentrant lock) recursive_mutex

The same lock can be locked multiple times in the same thread. Avoid some unnecessary deadlock
usage scenarios: a function is called recursively, and the function accesses the shared data to lock, recursively calls itself before unlocking, and the recursive lock will be used. In another scenario, in a class, two member functions f1 and f2 will access shared data and will be locked. Calling f2 after locking in f1 also requires a recursive lock. The implication is that as long as the locking operation of the same lock is called before unlocking, in order to prevent deadlock, a recursive lock is required.

Reference: https://www.zhihu.com/question/448190927/answer/1768286353

recursive_mutex rmux;
void Task1()
{
    
    
    rmux.lock();
    cout << "task1 [in]" << endl;
    rmux.unlock();
}
void Task2()
{
    
    
    rmux.lock();
    cout << "task2 [in]" << endl;
    rmux.unlock();
}
void ThreadMainRec(int i)
{
    
    
    for (;;)
    {
    
    
        rmux.lock(); 
        Task1();
        cout << i << "[in]" << endl;
        this_thread::sleep_for(2000ms);
        Task2();
        rmux.unlock();
        this_thread::sleep_for(1ms);
    }
}

Even if it is a recursive lock, it needs to be unlocked several times after being locked.

int main(int argc, char* argv[])
{
    
    
    
    for (int i = 0; i < 3; i++)
    {
    
    
        thread th(ThreadMainRec, i + 1);
        th.detach();
    }
    getchar();
 }  

It can be seen that ThreadMainRec No. 123 is switched alternately, and each ThreadMainRec executes task1 and task2 once
insert image description here

3. Shared lock shared_mutex

We often encounter such a scenario:
many (100) threads read a resource at the same time, there will be no problem. When a thread modifies this resource, these 100 resources must wait. At this time, there are other resources that want to modify this resource. Resource modification also needs to wait

To sum up:
the thread that reads resources can be read by other threads when reading, but cannot write.
The thread that writes resources can neither read nor write when other threads are writing.
Therefore, threads with different needs use different locks, and threads that read use locks that are read. The writing thread uses the writing lock. If
a thread is only reading, it uses the reading lock. If
a thread is going to write, it first uses the reading lock, then the writing lock, and then modifies the resource.

Now sacrifice these two locks:
c++14 shared timed mutex shared_timed_mutex
c++17 shared mutex shared_mutex

Take the C++14 standard as an example:
shared_timed_mutex stmux;
lock for writing: stmux.lock();
lock for reading: stmux.lock_shared();

shared_timed_mutex stmux;

void ThreadRead(int i)
{
    
    
    for (;;)
    {
    
    
        stmux.lock_shared();
        cout << i << " Read" << endl;
        //this_thread::sleep_for(500ms);
        stmux.unlock_shared();
        this_thread::sleep_for(1ms);
    }
}
void ThreadWrite(int i)
{
    
    
    for (;;)
    {
    
    
        stmux.lock(); //互斥锁 写入
        cout << i << " Write" << endl;
        this_thread::sleep_for(300ms);
        stmux.unlock();
        this_thread::sleep_for(1ms);
    }
}
int main(int argc, char* argv[])
{
    
    
    for (int i = 0; i < 3; i++)
    {
    
    
        thread th(ThreadWrite, i + 1);
        th.detach();
    }
    for (int i = 0; i < 3; i++)
    {
    
    
        thread th(ThreadRead, i + 1);
        th.detach();
    }
    getchar();
    return 0;
}

If you want to get the write lock, you must first ensure that the read lock is released and the write lock is released. If
you want to get the read lock, you only need to ensure that the write lock is released.
You can see that the read thread often appears in the same row, and the three read threads pass through After the for loop is created, if the resource is not locked by the write lock, all three threads can take the read lock and enter the thread task of cout together. When a thread's cout << i << " Read" << endl ;When the execution is not finished (more than one line after assembly), another thread's cout << i << " Read" << endl; is sent to the CPU to run, so the phenomenon of the same line will appear, but the writing thread will not. He wants to ensure that there are no reading threads or threads currently.
insert image description here
shared_lock C++14 implements a removable shared mutex ownership wrapper:

int main(int argc, char* argv[])
{
    
    

    {
    
    
        //共享锁
        static shared_timed_mutex  tmux;
        //读取锁 共享锁
        {
    
    
            //调用共享锁 
            shared_lock<shared_timed_mutex> lock(tmux);
            cout << "read data" << endl;
            //退出栈区 释放共享锁
        }
        //写入锁 互斥锁
        {
    
    
            unique_lock<shared_timed_mutex> lock(tmux);
            cout << "write data" << endl;
        }
    }
    return 0;
}

shared_lock<shared_timed_mutex> lock(tmux); shared lock on the incoming mutex in the constructor

explicit shared_lock(mutex_type& _Mtx)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(true) {
    
     // construct with mutex and lock shared
        _Mtx.lock_shared();
    }

unique_lock<shared_timed_mutex> lock(tmux); Mutex on the incoming mutex in the constructor

explicit unique_lock(_Mutex& _Mtx) : _Pmtx(_STD addressof(_Mtx)), _Owns(false) {
    
     // construct and lock
        _Pmtx->lock();
        _Owns = true;
    }

insert image description here

Guess you like

Origin blog.csdn.net/qq_42567607/article/details/125530759