C++11 multi-threaded programming three: lock resource management and condition variables

C++11 Multithreaded Programming 1: Overview of Multithreading

C++11 multi-threaded programming 2: multi-thread communication, thread synchronization, locks

C++11 multi-threaded programming three: lock resource management and condition variables 

C/C++ basics, Boost thread creation, thread synchronization 


3.1 Manually implement RAii management mutex resource lock automatic release

        The code written by myself is generally locked and released by itself, but it is especially easy to forget to release it, which will cause deadlock (it can be understood as a lock that has not been released). In order to ensure that every lock can be released, so it is With such a technology, called RAii resource acquisition and initialization, the feature is to use local objects to manage resources . We all know that local objects will be released once they are popped out of the stack. The code in a pair of braces is generated inside Variables and objects, once the pair of curly braces are out, they will be released. If it is an object of a class, it will call the destructor to release. This part of the code is called the space generated in the stack, so their The operating cycle is maintained by the operating system.
        The technology of using local objects to manage resources is called resource acquisition and initialization; its life cycle is managed by the operating system without manual intervention; the destruction of resources is easy to forget, resulting in deadlock or memory leaks.

#include <thread>
#include <iostream>
#include <string>
#include <mutex>
#include <shared_mutex>
//Linux -lpthread
using namespace std;
// RAII 这个是把上锁和解锁的过程放在类当中
class XMutex
{
public:
    XMutex(mutex& mux) :mux_(mux)
    {
        cout << "Lock" << endl;
        mux.lock();
    }
    ~XMutex()
    {
        cout << "Unlock" << endl;
        mux_.unlock();
    }
private:
    mutex& mux_;
};
static mutex mux;
void TestMutex(int status)
{
    XMutex lock(mux);
    if (status == 1)
    {
        cout << "=1" << endl;
        return;
    }
    else
    {
        cout << "!=1" << endl;
        return;
    }
}
int main(int argc, char* argv[])
{
    TestMutex(1);
    TestMutex(2);
 
    getchar();
    return 0;
}

 

        The class name is followed by a lock(mux). Lock is actually an object, and an actual parameter is passed to the parameterized constructor. 


3.2 RAIL control lock guard that comes with c++11

#include <thread>
#include <iostream>
#include <string>
#include <mutex>
#include <shared_mutex>
//Linux -lpthread
using namespace std;
 
static mutex gmutex; //这里可以是任意的互斥变量类型,只要里面包含lock函数的都可以
void TestLockGuard(int i)
{
    gmutex.lock();
    {
        //假如外部已经有锁,那就不lock
        lock_guard<mutex> lock(gmutex, adopt_lock);
        //出了大括号,调用析构函数,释放锁
    }
    {
        lock_guard<mutex> lock(gmutex);
        cout << "begin thread " << i << endl;
    }
    for (;;)
    {
        {
            lock_guard<mutex> lock(gmutex);
            cout << "In " << i << endl;
        }
        this_thread::sleep_for(500ms);
    }
}
int main(int argc, char* argv[])
{
    for (int i = 0; i < 3; i++)
    {
        thread th(TestLockGuard, i + 1);
        th.detach();
    }
 
    getchar();
    return 0;
}

 


3.3 unique_lock can temporarily unlock a mutex that controls timeout

        There will be several situations in the actual demand, that is, there may be mobile assignment, that is, assigning a lock to another lock, which may appear in the process of object assignment. If you want to support this transfer, then To use unique_lock , this is another tool for lock management. We used curly braces to control it before. If in the code, due to the needs of business logic, it needs to be unlocked first and then locked again, you can use the provided The interface is manually unlocked, and then the lock is added, and finally the lock is released by the destructor. It also supports more complex situations.
 
unique_lock C++11 implements a removable mutex ownership wrapper.
Supports temporary release of locks. unlock
supports adopt_lock ( Already own the lock, if you don’t lock it, the stack area will be released)
support defer_lock (delayed possession, no lock, stack area will not be released)
support try_to_lock try to obtain the ownership of the mutex without blocking, if the acquisition fails, exit the stack area will not Release, judged by the owns_lock() function.
Supports timeout parameters. The lock will not be owned after timeout.

When releasing the lock resource later, it will first determine whether it owns the lock resource. The code is as follows:

Source code for trying to lock:

As can be seen from the red box, the try_lock() function will return TRUE or FALSE, so it becomes the same as the above two.

#include <thread>
#include <iostream>
#include <string>
#include <mutex>
#include <shared_mutex>
//Linux -lpthread
using namespace std;
 
int main(int argc, char* argv[])
{
    {
        static mutex mux;
        {
            unique_lock<mutex> lock(mux);//创建lock对象的时候就已经加锁了
            lock.unlock();//可以临时释放锁
            lock.lock();  //也可以临时加锁
        }
        {
            //已经拥有锁 就不再锁定了,退出栈区解锁
            mux.lock();
            unique_lock<mutex> lock(mux, adopt_lock);
        }
        {
            //延后加锁 不拥有 退出栈区不解锁
            unique_lock<mutex> lock(mux, defer_lock);
            //后面需要我们主动的去加锁 退出栈区解锁
            lock.lock();
        }
        {
            //mux.lock();
            //尝试加锁 不阻塞 失败不拥有锁(退出栈区不解锁) 成功的话就拥有锁
            unique_lock<mutex> lock(mux, try_to_lock);
 
            if (lock.owns_lock())
            {
                cout << "owns_lock" << endl;
            }
            else
            {
                cout << "not owns_lock" << endl;
            }
        }
    }
    return 0;
}

 


3.4 C++14 shared lock shared lock wrapper

#include <thread>
#include <iostream>
#include <string>
#include <mutex>
#include <shared_mutex>
//Linux -lpthread
using namespace std;
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;
        }
    }
    getchar();
    return 0;
}

3.5 c++17 scoped_lock solves deadlock caused by interlock

        This wrapper is supported in C++17, remember to set the C++17
        deadlock problem: as shown in the figure below, thread A holds resource 2, thread B holds resource 1, and their respective locks are not unlocked , they apply for each other's resources at the same time. Since they cannot obtain the lock resources, the two threads will wait for each other and enter a deadlock state.

 simulate deadlock

#include <thread>
#include <iostream>
#include <string>
#include <mutex>
#include <shared_mutex>
//Linux make: g++ -std=c++14 -o main main8.c -lpthread
using namespace std;

static mutex mux1;
static mutex mux2;

void TestScope1(){
    //模拟死锁 停100ms等另一个线程锁mux2
    //这里只是使用sleep函数模拟业务逻辑,真正的业务当中不应该有sleep,因为这个是占用CPU资源的。
    this_thread::sleep_for(100ms);
    cout << this_thread::get_id() << " TestScope1 mux1 lock" << endl;
    mux1.lock();
    cout << this_thread::get_id() << " begin mux2 lock" << endl;
    mux2.lock(); //死锁
    cout << "TestScope1" << endl;
    this_thread::sleep_for(1000ms);
    mux1.unlock();
    mux2.unlock();
}
//上锁的顺序:2->1->2->1
void TestScope2(){
    cout << this_thread::get_id() << " TestScope2 mux2 lock" << endl;
    mux2.lock();
    this_thread::sleep_for(500ms);
    cout << this_thread::get_id() << " begin mux1 lock" << endl;
    mux1.lock();//死锁
    cout << "TestScope2" << endl;
    this_thread::sleep_for(1500ms);
    mux1.unlock();
    mux2.unlock();
}
 
int main(int argc, char* argv[]){	//演示死锁情况
    {
        {
            thread th(TestScope1);
            th.detach();
        }
        {
            thread th(TestScope2);
            th.detach();
        }
    }
    getchar();
    return 0;
}

 

Solve the deadlock problem: leave other codes unchanged, only modify the code of TestScope1() 

#include <thread>
#include <iostream>
#include <string>
#include <mutex>
#include <shared_mutex>
//Linux make: g++ -std=c++17 -o main main8.c -lpthread
using namespace std;
 
static mutex mux1;
static mutex mux2;
 
void TestScope1(){
    //模拟死锁 停100ms等另一个线程锁mux2
    //这里只是使用sleep函数模拟业务逻辑,真正的业务当中不应该有sleep,因为这个是占用CPU资源的。
    this_thread::sleep_for(100ms);
    cout << this_thread::get_id() << " begin mux1 lock" << endl;
    //mux1.lock();
    cout << this_thread::get_id() << " begin mux2 lock" << endl;
    //mux2.lock(); //死锁
 
    //c++11
    //这种,它必须两个同时锁住,才会进行下一步操作,如果没有会释放锁,这样就不会占用到两个锁
    //lock(mux1, mux2);
    //c++17
    scoped_lock lock(mux1, mux2); // 解决死锁,可以传多个锁。
 
    cout << "TestScope1" << endl;
    this_thread::sleep_for(1000ms);
    //mux1.unlock();
    //mux2.unlock();
}
//上锁的顺序:2->1->2->1
void TestScope2(){
    cout << this_thread::get_id() << " begin mux2 lock" << endl;
    mux2.lock();
    this_thread::sleep_for(500ms);
    cout << this_thread::get_id() << " begin mux1 lock" << endl;
    mux1.lock();//死锁
    cout << "TestScope2" << endl;
    this_thread::sleep_for(1500ms);
    mux1.unlock();
    mux2.unlock();
}
 
int main(int argc, char* argv[]){
    {
        //演示死锁情况
        {
            thread th(TestScope1);
            th.detach();
        }
        {
            thread th(TestScope2);
            th.detach();
        }
    }
    getchar();
    return 0;
}

 


3.7 Condition variable application scenario producer consumer signal processing

producer-consumer model

The producer and the consumer share resource variables (list queue), the producer produces a product, notifies the consumer to consume, the consumer blocks and waits for the signal - consumes the product after getting the signal (takes out the data in the list queue)

The key point of the core is that producers and consumers are in different threads, and there may be multiple producers and consumers. How do they communicate with each other, and even balance multiple threads to allocate them?
What did previous producers and consumers do? After the producer sends the data, the consumer receives it according to a fixed delay, and the two are independent of each other. Now, once the producer produces the data, the consumer is immediately notified to process it. And this notification is a semaphore. With this, the consumer can be blocked there, and the blocking does not occupy CPU resources. The semaphore is used to notify the consumer to unblock.


Producer model steps:

semaphore ready: std::condition_variable cv;
 1 acquire std::mutex (often via std::unique_lock): unique_lock lock(mux);
 2 modify while acquiring lock: msgs_.push_back(data);
 3 release lock And notify the reading thread: lock.unlock();
                                       cv.notify_one(); //notify a thread waiting for a signal
                                       cv.notify_all(); //notify all threads waiting for a signal

Consumer model steps:

 1 Obtain the mutex common to the thread that changes the shared variable: unique_lock lock(mux);
 2 wait() Wait for signal notification:
            2.1 No lambada expression
            2.2 Lambada expression cv.wait(lock, [] {return !msgs_.empty() ;});


3.8 condition variable code example reading and writing thread 

Simulate one writing thread and multiple reading threads

#include <thread>
#include <iostream>
#include <mutex>
#include <list>
#include <string>
#include <sstream>
#include <condition_variable> // std::condition_variable
 
using namespace std;
list<string> msgs_;
mutex mux;
condition_variable cv;	//信号量的定义

void ThreadWrite(){
    for(int i = 0;; i++){
        stringstream ss;
        ss << "Write msg " << i;        //字符串拼接,把"Write msg "和i拼在一起,并存放在ss中
        unique_lock<mutex> lock(mux);   //在这里加锁,确保锁住
        msgs_.push_back(ss.str());      //ss.str()<=>ss.data()都返回当前字符串的内容
        //这里为什么要先进行解锁呢?假如这里没有解锁,那么notify_one在调用的时候,在下面
        //读取线程中的wait函数,wait他需要先锁定,而这里的又没有释放,那么程序就会阻塞,就会造成死锁
        lock.unlock();                  //释放锁
        //cv.notify_one();                //发送信号,通知一个读线程进入
        cv.notify_all();              //即使发送通知信号给所有线程,也只有一个线程能进入
        this_thread::sleep_for(500ms);  //每500ms写一个数据,然后通知读取线程去处理
    }
}

void ThreadRead(int i){
    for(;;){
        cout << "开始读取数据" << endl;
        unique_lock<mutex> lock(mux);//加锁
        //这个wait什么时候解除阻塞,需要等到cv.notify_one();按次序通知到他,
        //如果是notify_all,所有在wait的线程都会返回"开始读取数据",最终还是只能有一个线程能进入读取
        //如果是notify_one就只有一个会返回"开始读取数据",最终还是只能有一个线程能进入读取
        cv.wait(lock, [i]
            {
                cout << "线程 " << i << " 等待" << endl;
                //msgs_.empty()当是空的时候返回TRUE,TRUE的话相当于阻塞是不应该进行的,看wait源码可知
                //return true;//当一直返回TRUE的话,不管有无信号wait都不会阻塞,而继续往下执行
                //当一直返回FALSE的话,没有信号的话连这个wait都不进入
                //有信号会一直阻塞在这儿,并按照信号间隔进入此wait函数
                //return false;
                return !msgs_.empty();
            });
        //获取信号后锁定
        while (!msgs_.empty()){
            cout << "线程 " << i << " 开始读取数据" << msgs_.front() << endl;
            msgs_.pop_front();
        }
    }
}

void threadread(int i){//比较简单的演示
    for(;;){
        cout << "read msg" << endl;
        unique_lock<mutex> lock(mux);//加锁
        cv.wait(lock);//是先解锁、阻塞等待信号,获取信号会锁定,所以做消息的处理是线程安全的
        //获取信号后锁定
        while (!msgs_.empty()){
            cout << "thread " << i << " read mumber " << msgs_.front() << endl;
            msgs_.pop_front();
        }
    }
}

int main(int argc, char* argv[]){
    thread th(ThreadWrite);
    th.detach();
    for (int i = 0; i < 3; i++){
        //thread th(ThreadRead, i + 1);//带lambda表达式的wait
        thread th(threadread, i + 1);//单纯的wait
        th.detach();
    }
    getchar();//放在这里面,阻塞住当前的操作
    return 0;
}

When changed to cv.notify_all();, the running result is like this: 

 

That is, all three threads respond, but only one enters the read. 

#include <thread>
#include <iostream>
#include <mutex>
#include <list>
#include <string>
#include <sstream>
#include <condition_variable> // std::condition_variable

using namespace std;
list<string> msgs_;
mutex mux;
condition_variable cv;
void ThreadWrite()
{
    for (int i = 0;; i++)
    {
        stringstream ss;
        ss << "Write msg " << i;        //字符串拼接,把"Write msg "和i拼在一起,并存放在ss中
        unique_lock<mutex> lock(mux);   //在这里加锁,确保锁住
        msgs_.push_back(ss.str());      //ss.str()<=>ss.data()都返回当前字符串的内容
        //这里为什么要先进行解锁呢?假如这里没有解锁,那么notify_one在调用的时候,在下面
        //读取线程中的wait函数,wait他需要先锁定,而这里的又没有释放,那么程序就会阻塞,就会造成死锁
        lock.unlock();                  //释放锁
        cv.notify_one();                //先解完锁后,发送信号,通知一个读线程进入
        //cv.notify_all();                //即使发送通知信号给所有线程,也只有一个线程能进入
        this_thread::sleep_for(3s);     //每3s写一个数据,然后通知读取线程去处理
    }
}
void ThreadRead(int i)
{
    for (;;)
    {
        cout << "开始读取数据" << endl;
        unique_lock<mutex> lock(mux);//加锁
        //这个wait什么时候解除阻塞,需要等到cv.notify_one();按次序通知到他,
        //如果是notify_all,所有在wait的线程都会返回"开始读取数据",最终还是只能有一个线程能进入读取
        //如果是notify_one就只有一个会返回"开始读取数据",最终还是只能有一个线程能进入读取
        cv.wait(lock, [i]
            {
                cout << "线程 " << i << " 等待" << endl;
                //msgs_.empty()当是空的时候返回TRUE,TRUE的话相当于阻塞是不应该进行的,看wait源码可知
                //return true;//当一直返回TRUE的话,不管有无信号wait都不会阻塞,而继续往下执行
                //当一直返回FALSE的话,没有信号的话连这个wait都不进入
                //有信号会一直阻塞在这儿,并按照信号间隔进入此wait函数
                //return false;
                return !msgs_.empty();
            });
        //获取信号后锁定
        while (!msgs_.empty())
        {
            cout << "线程 " << i << " 开始读取数据" << msgs_.front() << endl;
            msgs_.pop_front();
        }
    }
}
 
void threadread(int i)//比较简单的演示
{
    for (;;)
    {
        cout << "read msg" << endl;
        unique_lock<mutex> lock(mux);//加锁
        cv.wait(lock);//是先解锁、阻塞等待信号,获取信号会锁定,所以做消息的处理是线程安全的
        //获取信号后锁定
        while (!msgs_.empty())
        {
            cout << "thread " << i << " read mumber " << msgs_.front() << endl;
            msgs_.pop_front();
        }
    }
}
int main(int argc, char* argv[])
{
    thread th(ThreadWrite);
    th.detach();
    for (int i = 0; i < 3; i++)
    {
        thread th(ThreadRead, i + 1);//带lambda表达式的wait
        //thread th(threadread, i + 1);//单纯的wait
        th.detach();
    }
    getchar();//放在这里面,阻塞住当前的操作
    return 0;
}

Guess you like

Origin blog.csdn.net/qq_34761779/article/details/129227531