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

tech2025-03-05  13

#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; }  

最新回复(0)