C++11多线程编程 5.互斥量概念、用法、死锁演示及解决详解

#include<iostream>
#include<thread>
#include<vector>
#include<string>
#include<mutex>
#include<list>
using namespace std;
class A
{
public:
    //把收到的消息(玩家命令)入到一个队列的线程。
    void inMsgRecvQueue() {
        for (int i = 0; i < 10000; i++) {
            cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
            /*my_mutex1.lock();
            my_mutex2.lock();*///产生死锁,实际工程这两个锁头不一定挨着,可能他们需要保护不同的数据共享块
            /*std::lock_guard<std::mutex> sbguard(my_mutex1);
            std::lock_guard<std::mutex> sbguard2(my_mutex2);*/

            std::lock(my_mutex1, my_mutex2);//相当于每个互斥量都调用了lock
            std::lock_guard<std::mutex> sbguard(my_mutex1, std::adopt_lock);//adopt_lock()参数表示
            std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock);

            msgRecvQueue.push_back(i);//假设数字i就是玩家收到的命令,我直接弄到消息队列里面
            /*my_mutex2.unlock();
            my_mutex1.unlock();*/
        }
        return;
    }

    bool outMsgLULProc(int &command) {
        //std::lock_guard<std::mutex> sbguard(my_mutex1);//sbguard是随便起的对象名
        //lock_guard()构造函数里执行了mutex::lock();    
        //lock_guard()析构函数里执行了mutex::unlock();    
        //可以加{}作用域,提前结束lock_guard()的生命周期来执行析构函数,执行unlock()
        //my_mutex.lock();这时候就不能用lock()和unlock() 

        /*std::lock_guard<std::mutex> sbguard(my_mutex1);
        std::lock_guard<std::mutex> sbguard2(my_mutex2);*/
        /*my_mutex2.lock();
        my_mutex1.lock();*/
        std::lock(my_mutex2, my_mutex1);
        std::lock_guard<std::mutex> sbguard(my_mutex1, std::adopt_lock);//adopt_lock()参数表示不通过lock_guard的构造函数调用这两个互斥量的构造函数,因为std::lock()已经lock()过了
        std::lock_guard<std::mutex> sbguard2(my_mutex2, adopt_lock);
        if (!msgRecvQueue.empty()) {
            //消息队列不会空
            command = msgRecvQueue.front();//返回第一个元素,但不检查元素是否存在
            msgRecvQueue.pop_front();//移除第一个元素。但不返回。
            /*my_mutex1.unlock();
            my_mutex2.unlock();*/
            return true;
        }
        /*my_mutex1.unlock();
        my_mutex2.unlock();*/
        return false;
    }

    //把数据从消息队列中取出的线程:
    void outMsgRecvQueue() {
        int command = 0;
        for (int i = 0; i < 10000; i++) {
            bool result = outMsgLULProc(command);
            if (result == true) {
                    cout << "outMsgRecvQueue执行了,取出一个元素,并且移除了一个元素" << command << endl;
                    //可以进行数据处理 
            }
            else
            {
                cout << "outMsgRecvQueue()执行,但目前消息队列为空" << i << endl;
            }
        }
        cout << "end" << endl;
    }
private:
    std::list<int>    msgRecvQueue;//容器,专门用于代表玩家给咱们发送的命令
    std::mutex my_mutex1;//创建了一个互斥量
    std::mutex my_mutex2;//创建了一个互斥量。两把锁才能产生死锁
};
int main()
{
    /*    保护共享数据,操作时,某个线程用代码把共享数据锁住,其他想操作共享数据的线程必须等待。等待解锁。解锁后,其他线程才能操作数据。
        (1)互斥量(mutex)的基本概念
            互斥量就是一个类对象。理解成一把锁多个线程尝试用互斥量这个类对象的成员函数lock()来加锁。只有一个线程能够锁定成功,(成功的标志是lock()的返回值))
                如果没有锁定成功,那么这个线程中的执行流程会卡在lock()这里,并且不断的尝试去锁这把锁头,锁成功后才能继续执行。
    *    (2)互斥量的用法
    *        (2.1)lock(),unlock()
    *            步骤:先lock(),操作共享数据,unlock();
    *            lock()和unlock()要成对使用,有lock()必然要有unlock(),每调用一次lock(),必然应该调用一次unlock();
    *            不应该也不允许调用一次lock()却调用两次unlock()等等,这些非对称数量的调用,都会导致代码不稳定甚至崩溃。
    *            有lock,忘记unlock的问题,非常难排查;
    *            为了防止大家忘记unlock,引入了一个std::lock_guard()的类模板:你忘了unlock()不要紧,我替你unlock()
    *            和指针之类类似:你忘记释放内存,我替你释放。 
    *        (2.2)std::lock_guard()类模板:可以直接取代lock()和unlock(),也就说你用了std::lock_guard()再不能使用lock()和unlock()
    *            可以用{}作用域来让lock_goard()提前执行析构函数,在作用域外面不要进行读写操作。
    *            std::lock_guard<std::mutex> sbguard(my_mutex);//sbguard是随便起的对象名
        (3)死锁
            C++中:一把锁就是一个互斥量
                比如我有两把锁,(死锁这个问题产生的前提条件是至少有两个互斥量,一个不会产生);金锁(jinlock),银锁(yinlock)
                    (1)两个线程A,B,线程A执行的时候,这个线程先锁金锁,把金锁lock()成功了,然后lock()银锁。。出现了上下文切换
                    (2)线程B执行的时候,这个线程先锁银锁,吧银锁lock()成功了,然后lock()金锁。此时此刻死锁就产生了
                    (3)线程A拿不到银锁头,流程走不下去(所以后边的代码有解锁金锁头的但是流程走不下去,所以金锁头解不开)
                    (3)线程B拿不到金锁头,流程走不下去(所以后边的代码有解锁银锁头的但是流程走不下去,所以银锁头解不开)                        
            (3.1)死锁演示
            (3.2)死锁的一般解决方案
                只要保证这两个互斥量上锁的顺序一致就不会死锁。    
            (3.3)std::lock()函数模板:用来处理多个互斥量,不过还是得用unlock一个一个解
                能力:一次能锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行)
                它不存在这种因为在多个线程中,因为锁的顺序问题导致死锁的风险问题;
                std::lock()如果互斥量中有一个没锁住,它就先把锁住的互斥量释放掉,它就在那里等着,等所有的互斥量都锁住,它才能往下走(返回)
                特点:要么两个都锁住,要么两个都没锁住。如果只锁了一个,另外一个没锁成功,则他立即把已经锁住的解锁。
            (3.4)std::lock_guard的std::adopt_lock参数
                std::adopt_lock()是一个结构体对象,表示这个互斥量已经lock()过了,不需要在std::lock_guard<std::mutex>里面对mutex对象进行lock(),析构上正常
                总结:std::lock()一次锁定多个互斥量;谨慎使用(建议一个一个锁),
    */            
    
    A myobj;
    std::thread mytobj(&A::inMsgRecvQueue, &myobj);//第二个参数是引用,才能保证线程里用的是同一个对象。
    std::thread mytobj2(&A::outMsgRecvQueue, &myobj);
    mytobj.join();
    mytobj2.join();
    return 0;
}
 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值