
(C++11/14/17学习笔记):互斥量概念、用法、死锁演示及解决详解
发布日期:2021-05-07 15:19:50
浏览次数:45
分类:精选文章
本文共 9065 字,大约阅读时间需要 30 分钟。
目录
互斥量概念、用法、死锁演示及解决详解
互斥量(mutex)的基本概念
- 保护共享大数据,操作时,某个线程 用代码把共享数据锁住、操作数据、解锁,其他想操作共享数据的线程必须等待解锁,锁定住,操作,解锁。
- 互斥量是个类对象。
- 理解成一把锁,多个线程尝试用 lock() 成员函数来加锁这把锁头,只有一个线程能锁定成功(成功的标志是lock()函数返回)。
- 如果没锁成功,那么流程阻塞在lock()这里不断的尝试去锁这把锁头。
- 互斥量使用要小心,保护数据不多也不少,少了,没达到保护效果,多了,影响效率。
互斥量的用法
- 引入头文件 #include <mutex>;
lock(), unlock()
- 步骤:先lock(), 操作共享数据,再unlock()。
- lock()和unlock()要成对使用,有lock必然要有unlock,每调用一次lock(),必然应该调用一次unlock()。
- 不应该也不允许调用1次lock()却调用了2次unlock(),也不允许调用2次lock却调用1次unlock(),这些非对称。
- 数量的调用都会导致代码不稳定甚至崩溃。有lock,忘记unlock的问题,非常难排查。
- 如果lock了,注意退出的地方(如 return)是不是加上了unlock,几个出口几个unlock。
#include#include #include #include #include #include
using namespace std;class A{public: //把收到的消息(玩家命令)加入到一个队列的线程 void inMsgRecvQueue() { for (int i = 1; i < 10000; ++i) { cout << "inMsgRecvQueue执行了,插入一个元素" << i << endl; my_mutex.lock(); msgRecvQueue.push_back(i); //假设这个数字就是玩家发来的命令,加入到消息队列中 my_mutex.unlock(); } } //在这个函数中加锁 bool outMsgMutPro(int& command ) { my_mutex.lock(); if (!msgRecvQueue.empty()) { //消息队列不为空 command = msgRecvQueue.front(); //返回第一个元素,但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素,但不返回 my_mutex.unlock(); return true; } my_mutex.unlock(); return false; } //把消息从消息队列中取出的线程 void outMsgRecvQueue() { int command{}; for (int i = 1; i < 10000; ++i) { bool ret = outMsgMutPro(command); if (ret) { cout << "outMsgMutPro执行了,取出一个元素" << command << endl; //这里就针对具体的命令具体处理 //... } else { //消息队列为空 cout << "outMsgRecvQueue执行了,但是当前消息队列为空" << i << endl; } } cout << "outMsgRecvQueue()执行完毕" << endl; }private: std::list msgRecvQueue; //容器(消息队列),专门代表玩家给我们发来的命令 std::mutex my_mutex;};int main(){ A obja; std::thread outMsgThread(&A::outMsgRecvQueue, &obja); //第二个参数是引用,保证线程里操作同一个对象 std::thread inMsgThread(&A::inMsgRecvQueue, &obja); inMsgThread.join(); outMsgThread.join(); //主线程执行 std::cout << "主线程结束" << std::endl; return 0;}
std::lock_guard类模板
- 为了防止大家忘记unlock(),引入了一个叫std::lock_guard的类模板:你忘记unlock不要紧,我替你unlock()。
- 联想智能指针(unique_ptr<>) : 你忘记释放内存不要紧,我给你释放。
- std::lock_guard 类模板:直接取代lock() 和unlock();也就是说, 你用了lock_guard之后,再不能使用lock()和unlock()了。
- lock_guard构造函数里执行了mutex::lock()。
- lock_guard析构函数里执行了mutex::unlock()。
- 结合 {} ,可以控制作用的范围(RAII)。
#include#include #include #include #include #include
using namespace std;class A{public: //把收到的消息(玩家命令)加入到一个队列的线程 void inMsgRecvQueue() { for (int i = 1; i < 10000; ++i) { cout << "inMsgRecvQueue执行了,插入一个元素" << i << endl; { std::lock_guard mutex_guard_in(my_mutex); msgRecvQueue.push_back(i); //假设这个数字就是玩家发来的命令,加入到消息队列中 } //其他代码... } } //在这个函数中加锁 bool outMsgMutPro(int& command ) { std::lock_guard mutex_guard_out(my_mutex); if (!msgRecvQueue.empty()) { //消息队列不为空 command = msgRecvQueue.front(); //返回第一个元素,但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素,但不返回 return true; } return false; } //把消息从消息队列中取出的线程 void outMsgRecvQueue() { int command{}; for (int i = 1; i < 10000; ++i) { bool ret = outMsgMutPro(command); if (ret) { cout << "outMsgMutPro执行了,取出一个元素" << command << endl; //这里就针对具体的命令具体处理 //... } else { //消息队列为空 cout << "outMsgRecvQueue执行了,但是当前消息队列为空" << i << endl; } } cout << "outMsgRecvQueue()执行完毕" << endl; }private: std::list msgRecvQueue; //容器(消息队列),专门代表玩家给我们发来的命令 std::mutex my_mutex;};int main(){ A obja; std::thread outMsgThread(&A::outMsgRecvQueue, &obja); //第二个参数是引用,保证线程里操作同一个对象 std::thread inMsgThread(&A::inMsgRecvQueue, &obja); inMsgThread.join(); outMsgThread.join(); //主线程执行 std::cout << "主线程结束" << std::endl; return 0;}
死锁
- 比如我有两把锁(死锁这个问题 是由至少两个锁头也就是两个互斥量才能产生):金锁(jinlock) 银锁(yinlock)。
- 两个线程 A,B
- (1) 线程A执行的时候,这个线程先锁金锁,把金锁lock()成功了,然后它去lock银锁。
- 出现了上下文切换
- (2) 线程B执行了,这个线程先锁银锁,因为银锁还没有被锁,所以银锁会lock()成功,线程B要去lock金锁。
- 此时此刻,死锁就产生了。
- (3) 线程A因为拿不到银锁头,流程走不下去(所有后边代码有解锁金锁锁头的但是流程走不下去,所以金锁头解不开)。
- (4) 线程B因为拿不到金锁头,流程走不下去(所有后边代码有解锁银锁锁头的但是流程走不下去,所以银锁头解不开)。
- 大家都晾在这里,你等我,我等你。
死锁演示
#include#include #include #include #include #include
using namespace std;class A{public: //把收到的消息(玩家命令)加入到一个队列的线程 void inMsgRecvQueue() { for (int i = 1; i < 10000; ++i) { cout << "inMsgRecvQueue执行了,插入一个元素" << i << endl; my_mutex2.lock(); //其他代码 my_mutex1.lock(); msgRecvQueue.push_back(i); //假设这个数字就是玩家发来的命令,加入到消息队列中 my_mutex1.unlock(); //其他代码 my_mutex2.unlock(); } } //在这个函数中加锁 bool outMsgMutPro(int& command ) { my_mutex1.lock(); //其他代码 my_mutex2.lock(); if (!msgRecvQueue.empty()) { //消息队列不为空 command = msgRecvQueue.front(); //返回第一个元素,但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素,但不返回 my_mutex2.unlock(); //其他代码 my_mutex1.unlock(); return true; } my_mutex2.unlock(); //其他代码 my_mutex1.unlock(); return false; } //把消息从消息队列中取出的线程 void outMsgRecvQueue() { int command{}; for (int i = 1; i < 10000; ++i) { bool ret = outMsgMutPro(command); if (ret) { cout << "outMsgMutPro执行了,取出一个元素" << command << endl; //这里就针对具体的命令具体处理 //... } else { //消息队列为空 cout << "outMsgRecvQueue执行了,但是当前消息队列为空" << i << endl; } } cout << "outMsgRecvQueue()执行完毕" << endl; }private: std::list msgRecvQueue; //容器(消息队列),专门代表玩家给我们发来的命令 std::mutex my_mutex1; std::mutex my_mutex2;};int main(){ A obja; std::thread outMsgThread(&A::outMsgRecvQueue, &obja); //第二个参数是引用,保证线程里操作同一个对象 std::thread inMsgThread(&A::inMsgRecvQueue, &obja); inMsgThread.join(); outMsgThread.join(); //主线程执行 std::cout << "主线程结束" << std::endl; return 0;}
死锁的一般解决方案
- 只要保证这两个互斥量上锁的顺序一致就不会死锁。
std::lock()函数模板
- 用来处理多个互斥量的时候才出场。
- 能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,1个不行)。
- 它不存在这种因为再多个线程中 因为锁的顺序问题导致死锁的风险问题。
- std::lock():
- 如果互斥量中有一个没锁住,它就在那里等着,等所有互斥量都锁住,它才能往下走(返回)。
- 要么两个互斥量都锁住,要么两个互斥量都没锁住。
- 如果只锁了一个,另外一个没锁成功,则它立即把已经锁住的解锁。
class A{public: //把收到的消息(玩家命令)加入到一个队列的线程 void inMsgRecvQueue() { for (int i = 1; i < 10000; ++i) { cout << "inMsgRecvQueue执行了,插入一个元素" << i << endl; std::lock(my_mutex1, my_mutex2); msgRecvQueue.push_back(i); //假设这个数字就是玩家发来的命令,加入到消息队列中 my_mutex1.unlock(); //其他代码 my_mutex2.unlock(); } } //在这个函数中加锁 bool outMsgMutPro(int& command ) { std::lock(my_mutex1, my_mutex2); if (!msgRecvQueue.empty()) { //消息队列不为空 command = msgRecvQueue.front(); //返回第一个元素,但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素,但不返回 my_mutex2.unlock(); //其他代码 my_mutex1.unlock(); return true; } my_mutex2.unlock(); //其他代码 my_mutex1.unlock(); return false; } //把消息从消息队列中取出的线程 void outMsgRecvQueue() { int command{}; for (int i = 1; i < 10000; ++i) { bool ret = outMsgMutPro(command); if (ret) { cout << "outMsgMutPro执行了,取出一个元素" << command << endl; //这里就针对具体的命令具体处理 //... } else { //消息队列为空 cout << "outMsgRecvQueue执行了,但是当前消息队列为空" << i << endl; } } cout << "outMsgRecvQueue()执行完毕" << endl; }private: std::list msgRecvQueue; //容器(消息队列),专门代表玩家给我们发来的命令 std::mutex my_mutex1; std::mutex my_mutex2;};
std::lock_guard的std::adopt_lock参数
- std::adopt_lock是个结构体对象,起一个标记作用:作用就是表示这个互斥量已经lock(),不需要再std::lock_guard<std::mutext>构造函数里 再面对对象进行再次lock()了。
class A{public: //把收到的消息(玩家命令)加入到一个队列的线程 void inMsgRecvQueue() { for (int i = 1; i < 10000; ++i) { cout << "inMsgRecvQueue执行了,插入一个元素" << i << endl; std::lock(my_mutex1, my_mutex2); std::lock_guardin_mutex_guard1(my_mutex1, std::adopt_lock); std::lock_guard in_mutex_guard2(my_mutex2, std::adopt_lock); msgRecvQueue.push_back(i); //假设这个数字就是玩家发来的命令,加入到消息队列中 //其他代码 } } //在这个函数中加锁 bool outMsgMutPro(int& command ) { std::lock(my_mutex1, my_mutex2); std::lock_guard out_mutex_guard1(my_mutex1, std::adopt_lock); std::lock_guard out_mutex_guard2(my_mutex2, std::adopt_lock); if (!msgRecvQueue.empty()) { //消息队列不为空 command = msgRecvQueue.front(); //返回第一个元素,但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素,但不返回 return true; } return false; } //把消息从消息队列中取出的线程 void outMsgRecvQueue() { int command{}; for (int i = 1; i < 10000; ++i) { bool ret = outMsgMutPro(command); if (ret) { cout << "outMsgMutPro执行了,取出一个元素" << command << endl; //这里就针对具体的命令具体处理 //... } else { //消息队列为空 cout << "outMsgRecvQueue执行了,但是当前消息队列为空" << i << endl; } } cout << "outMsgRecvQueue()执行完毕" << endl; }private: std::list msgRecvQueue; //容器(消息队列),专门代表玩家给我们发来的命令 std::mutex my_mutex1; std::mutex my_mutex2;};
- std::lock():一次锁定多个互斥量,谨慎使用(建议一个一个锁)。
发表评论
最新留言
路过,博主的博客真漂亮。。
[***.116.15.85]2025年04月19日 07时12分08秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
PHP 脚本不报错
2021-05-09
代码整洁之道小结
2021-05-09
悲观锁与乐观锁
2021-05-09
js new Date 创建时间默认是8点
2021-05-09
Python实现cmd命令连续执行
2021-05-09
罗马数字
2021-05-09
IO多路复用小故事
2021-05-09
纠错码简介
2021-05-09
码云 Pages 搭建
2021-05-09
《论可计算数及其在判定上的应用》简单理解
2021-05-09
中国剩余定理证明过程
2021-05-09
kafka告警简单方案
2021-05-09
java接口的应用举例
2021-05-09
java接口中多继承的问题
2021-05-09
java中Object.equals()简单用法
2021-05-09
一个小例子对多态简单的理解
2021-05-09
poj 2187 Beauty Contest(凸包求解多节点的之间的最大距离)
2021-05-09
poj 2492A Bug's Life(并查集)
2021-05-09
ZZUOJ 1199 大小关系(拓扑排序,两种方法_判断入度和dfs回路判断)
2021-05-09