C++并行开发5-互斥量概念、用法、死锁演示及解决详解

FengLY Lv3

1. 互斥量

互斥量就是个类对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁定成功,如果没有锁成功,那么流程将卡在lock()这里不断尝试去锁定。
使用互斥量的一个代码示例:

1.1 使用lock, unlock

#include <iostream>
#include <thread>
#include <algorithm>
#include <vector>
#include <list>
#include <mutex>
using namespace std;

//调用类对象的成员函数的方式来创建线程
class A {
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 10000; i++) {
            my_mutex.lock();
            cout << "消息队列插入一个元素 " << i << endl;
            msgRecvQueue.push_back(i);
            my_mutex.unlock();
        }
    }

    bool judgeOutMsg(int &command) {
        my_mutex.lock();
        if (!msgRecvQueue.empty()) {
            //消息队列非空
            command = msgRecvQueue.front();  //取出第一个元素
            msgRecvQueue.pop_front();
            my_mutex.unlock();   //这个unlock不能忘
            return true;
        }
        else {
            my_mutex.unlock();
            return false;
        }
    }

    void outMsgRecgQueue() {
        int commond = 0;
        for (int i = 0; i < 10000; i++) {
            bool result = judgeOutMsg(commond);
            if (result) {
                cout << "消息队列中取出了一个元素" << commond << endl;
            }
            else {
                cout << "消息队列为空" << endl;
            }
        }
    }

private:
    list<int> msgRecvQueue;  //消息队列
    std::mutex my_mutex;// 创建一个互斥量
};



int main()
{
    A myobja;
    std::thread myOutnMsgObj(&A::outMsgRecgQueue, &myobja);
    std::thread myInnMsgObj(&A::inMsgRecvQueue, &myobja);

    //这里必须使用引用,否则线程会重新拷贝构造一个对象,
    //但是使用引用就需要考虑类对象的使用周期,必须在主线程使用结束

    myOutnMsgObj.join();
    myInnMsgObj.join();

    //保护共享数据,操作时候用代码将共享数据锁住,其他想操作共享数据的线程必须等待解锁
    //(一) mutex的基本概念
    // lock unlock 应该成对出现

    return 0;
}

1.2 使用lock_guard类模板

#include <iostream>
#include <thread>
#include <algorithm>
#include <vector>
#include <list>
#include <mutex>
using namespace std;

//调用类对象的成员函数的方式来创建线程
class A {
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 10000; i++) {
            //my_mutex.lock();
            {
                std::lock_guard<std::mutex> sbguard(my_mutex);
                cout << "消息队列插入一个元素 " << i << endl;
                msgRecvQueue.push_back(i);
                //my_mutex.unlock();
            }
        }
    }

    bool judgeOutMsg(int &command) {
        std::lock_guard<std::mutex> sbguard(my_mutex);  
        //构造函数里面执行了lock, 析构函数里执行了unlock
        //my_mutex.lock();
        if (!msgRecvQueue.empty()) {
            //消息队列非空
            command = msgRecvQueue.front();  //取出第一个元素
            msgRecvQueue.pop_front();
            //my_mutex.unlock();   //这个unlock不能忘
            return true;
        }
        else {
            //my_mutex.unlock();
            return false;
        }
    }

    void outMsgRecgQueue() {
        int commond = 0;
        for (int i = 0; i < 10000; i++) {
            bool result = judgeOutMsg(commond);
            if (result) {
                cout << "消息队列中取出了一个元素" << commond << endl;
            }
            else {
                cout << "消息队列为空" << endl;
            }
        }
    }

private:
    list<int> msgRecvQueue;  //消息队列
    std::mutex my_mutex;// 创建一个互斥量
};



int main()
{
    A myobja;
    std::thread myOutnMsgObj(&A::outMsgRecgQueue, &myobja);
    std::thread myInnMsgObj(&A::inMsgRecvQueue, &myobja);

    //这里必须使用引用,否则线程会重新拷贝构造一个对象,
    //但是使用引用就需要考虑类对象的使用周期,必须在主线程使用结束

    myOutnMsgObj.join();
    myInnMsgObj.join();

    //保护共享数据,操作时候用代码将共享数据锁住,其他想操作共享数据的线程必须等待解锁
    //(一) mutex的基本概念
    // (1)lock unlock 应该成对出现
    // (2)lock_guard 可以取代lock 和 unlock

    return 0;
}

2. 死锁

2.1 死锁演示

#include <iostream>
#include <thread>
#include <algorithm>
#include <vector>
#include <list>
#include <mutex>
using namespace std;

//调用类对象的成员函数的方式来创建线程
class A {
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; i++) {
            my_mutex1.lock();
            my_mutex2.lock();
            //{
                //std::lock_guard<std::mutex> sbguard(my_mutex);
            cout << "消息队列插入一个元素 " << i << endl;
                //msgRecvQueue.push_back(i);
                //my_mutex.unlock();
            //}
            my_mutex1.unlock();
            my_mutex2.unlock();

        }
    }

    bool judgeOutMsg(int &command) {
        //std::lock_guard<std::mutex> sbguard(my_mutex1);
        //构造函数里面执行了lock, 析构函数里执行了unlock
        my_mutex2.lock();
        my_mutex1.lock();
        if (!msgRecvQueue.empty()) {
            //消息队列非空
            command = msgRecvQueue.front();  //取出第一个元素
            msgRecvQueue.pop_front();
            //my_mutex.unlock();   //这个unlock不能忘
            my_mutex1.unlock();
            my_mutex2.unlock();
            return true;
        }
        else {
            my_mutex1.unlock();
            my_mutex2.unlock();
            //my_mutex.unlock();
            return false;
        }
    }

    void outMsgRecgQueue() {
        int commond = 0;
        for (int i = 0; i < 100000; i++) {
            bool result = judgeOutMsg(commond);
            if (result) {
                cout << "消息队列中取出了一个元素" << commond << endl;
            }
            else {
                cout << "消息队列为空" << endl;
            }
        }
    }

private:
    list<int> msgRecvQueue;  //消息队列
    std::mutex my_mutex1;// 创建一个互斥量
    std::mutex my_mutex2;// 创建一个互斥量
};



int main()
{
    A myobja;
    std::thread myOutnMsgObj(&A::outMsgRecgQueue, &myobja);
    std::thread myInnMsgObj(&A::inMsgRecvQueue, &myobja);
    myOutnMsgObj.join();
    myInnMsgObj.join();

    return 0;
}

2.2 死锁的一般解决方案

lock顺序保持一致即可

2.3 std::lock

#include <iostream>
#include <thread>
#include <algorithm>
#include <vector>
#include <list>
#include <mutex>
using namespace std;

//调用类对象的成员函数的方式来创建线程
class A {
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; i++) {
            /*my_mutex1.lock();
            my_mutex2.lock();*/
            std::lock(my_mutex1, my_mutex2);
            std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);  //前面已经锁过了,这里需要adopt_lock
            std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock);

            //{
                //std::lock_guard<std::mutex> sbguard(my_mutex);
            cout << "消息队列插入一个元素 " << i << endl;
                //msgRecvQueue.push_back(i);
                //my_mutex.unlock();
            //}
            /*my_mutex1.unlock();
            my_mutex2.unlock();*/

        }
    }

    bool judgeOutMsg(int &command) {
        //std::lock_guard<std::mutex> sbguard(my_mutex1);
        //构造函数里面执行了lock, 析构函数里执行了unlock
        /*my_mutex2.lock();
        my_mutex1.lock();*/
        std::lock(my_mutex1, my_mutex2);
        if (!msgRecvQueue.empty()) {
            //消息队列非空
            command = msgRecvQueue.front();  //取出第一个元素
            msgRecvQueue.pop_front();
            //my_mutex.unlock();   //这个unlock不能忘
            my_mutex1.unlock();
            my_mutex2.unlock();
            return true;
        }
        else {
            my_mutex1.unlock();
            my_mutex2.unlock();
            //my_mutex.unlock();
            return false;
        }
    }

    void outMsgRecgQueue() {
        int commond = 0;
        for (int i = 0; i < 100000; i++) {
            bool result = judgeOutMsg(commond);
            if (result) {
                cout << "消息队列中取出了一个元素" << commond << endl;
            }
            else {
                cout << "消息队列为空" << endl;
            }
        }
    }

private:
    list<int> msgRecvQueue;  //消息队列
    std::mutex my_mutex1;// 创建一个互斥量
    std::mutex my_mutex2;// 创建一个互斥量
};



int main()
{
    A myobja;
    std::thread myOutnMsgObj(&A::outMsgRecgQueue, &myobja);
    std::thread myInnMsgObj(&A::inMsgRecvQueue, &myobja);

    //这里必须使用引用,否则线程会重新拷贝构造一个对象,
    //但是使用引用就需要考虑类对象的使用周期,必须在主线程使用结束

    myOutnMsgObj.join();
    myInnMsgObj.join();

    //保护共享数据,操作时候用代码将共享数据锁住,其他想操作共享数据的线程必须等待解锁
    //(一) mutex的基本概念
    // (1)lock unlock 应该成对出现
    // (2)lock_guard 可以取代lock 和 unlock
    //  (3) 可以使用std::lock 代替成对出现的互斥量

    return 0;
}

lock_guard : adopt_lock参数表示前面已经锁过了,这里需要adopt_lock std::lock表示可以同时锁住多个锁

  • Title: C++并行开发5-互斥量概念、用法、死锁演示及解决详解
  • Author: FengLY
  • Created at : 2023-06-18 22:40:09
  • Updated at : 2023-06-18 22:56:50
  • Link: https://zhouaq.com/2023/06/18/C++并行开发5-互斥量概念、用法、死锁演示及解决详解/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments