多线程互斥量以及死锁问题解决详解
2019-03-10 15:02
141 查看
多线程互斥量以及死锁问题解决详解
1.互斥量(mutex)的基本概念
互斥量可以理解成一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程可以锁定成功(成功的标志有lock()返回),如果没有锁成功,那么就会在lock()这里不断的尝试。
#include <mutex>
2.互斥量的用法
(1)lock(),unlock()
步骤:先lock()给共享数据加锁,操作共享数据,然后再unlock()解锁。
规则:lock()和unlock()要成对使用,每调用一次lock(),不然调用一次unlock()。
解决共享数据访问冲突问题:
#include<iostream> #include<list> #include<thread> #include<mutex> class A { public: //把收到的消息(玩家命令)写入到一个队列的线程 void inMsgRecvQueue() { for (int i = 0; i < 100000; ++i) { std::cout << "inMsgRecvQueue()执行,插入一个元素" << i << std::endl; m_mutex.lock(); msgRecvQueue.push_back(i);//假设数字i就是收到的命令 m_mutex.unlock(); } } bool outMsgLockPro(int &command) { m_mutex.lock(); if (!msgRecvQueue.empty()) { int command = msgRecvQueue.front(); msgRecvQueue.pop_front();//移除list首元素 m_mutex.unlock(); return true; } m_mutex.unlock(); return false; } //把收据从消息队列中取出的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; ++i) { bool result = outMsgLockPro(command); if (result == true) { std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl; //可以考虑进行命令(数据)处理 } else { std::cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << std::endl; } } std::cout << "END" << std::endl; } private: std::list<int>msgRecvQueue;//存储玩家发送过来的命令--消息队列 std::mutex m_mutex;//创建互斥量 }; int main() { A myobj; std::thread m_outMsgThread(&A::outMsgRecvQueue, &myobj); std::thread m_inMsgThread(&A::inMsgRecvQueue, &myobj); m_outMsgThread.join(); m_inMsgThread.join(); getchar(); return 0; }
(2)std::lock_guard类模板
为了防止忘记unlock(),引入
std::lock_guard的类模板,当忘记unlock()时,std::lock_guard来执行unlock()。
当使用
std::lock_guard类模板时,就不用再使用lock()和unlock()对共享数据进行加锁。
原理是:lock_guard类构函数中执行了mutex::lock(),而其析构函数中执行了mutex::unlock()。
缺陷:没有lock()和unlock()灵活。
#include<iostream> #include<vector> #include<list> #include<thread> #include<mutex> class A { public: //把收到的消息(玩家命令)写入到一个队列的线程 void inMsgRecvQueue() { for (int i = 0; i < 100000; ++i) { std::cout << "inMsgRecvQueue()执行,插入一个元素" << i << std::endl; { std::lock_guard<std::mutex>lockGuard(m_mutex); msgRecvQueue.push_back(i);//假设数字i就是收到的命令 } //m_mutex.lock(); //msgRecvQueue.push_back(i);//假设数字i就是收到的命令 //m_mutex.unlock(); } } bool outMsgLockPro(int &command) { std::lock_guard<std::mutex> lockGuard(m_mutex); //m_mutex.lock(); if (!msgRecvQueue.empty()) { int command = msgRecvQueue.front(); msgRecvQueue.pop_front();//移除list首元素 //m_mutex.unlock(); return true; } //m_mutex.unlock(); return false; } //把收据从消息队列中取出的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; ++i) { bool result = outMsgLockPro(command); if (result == true) { std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl; //可以考虑进行命令(数据)处理 } else { std::cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << std::endl; } } std::cout << "END" << std::endl; } private: std::list<int>msgRecvQueue;//存储玩家发送过来的命令--消息队列 std::mutex m_mutex;//创建互斥量 }; int main() { A myobj; std::thread m_outMsgThread(&A::outMsgRecvQueue, &myobj); std::thread m_inMsgThread(&A::inMsgRecvQueue, &myobj); m_outMsgThread.join(); m_inMsgThread.join(); getchar(); return 0; }
3.死锁
(1)死锁演示
死锁问题是由至少两个锁头也就是两个互斥量(mutex)才能产生。
死锁案例:如有两互斥量lock1,lock2,且有两个线程A,B。
① 在线程A执行的时候,此线程先锁lock1并且成功了,这个时候准备去锁lock2…
② 此时,出现了上下文切换,线程B开始执行。这个线程先锁lock2,因为lock2还没有被锁,所以lock2会lock()成功。于是,线程B要去锁lock1…
③ 此时,线程A因为拿不到锁lock2,流程走不下去(虽然后面代码有unlock锁lock1的,但是流程走不下去,所以lock1解不开);同理,线程B因为拿不到锁lock1,流程走不下去(虽然后面代码有unlock锁lock2的,但是流程走不下去,所以lock2解不开)。这样,死锁就产生了。
死锁的实例如下:
#include<iostream> #include<vector> #include<list> #include<thread> #include<mutex> class A { public: //把收到的消息(玩家命令)写入到一个队列的线程 void inMsgRecvQueue() { for (int i = 0; i < 100000; ++i) { m_mutex1.lock(); m_mutex2.lock(); msgRecvQueue.push_back(i);//假设数字i就是收到的命令 m_mutex2.unlock(); m_mutex1.unlock(); } } bool outMsgLockPro(int &command) { m_mutex2.lock(); m_mutex1.lock(); if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); msgRecvQueue.pop_front();//移除list首元素 m_mutex1.unlock(); m_mutex2.unlock(); return true; } m_mutex1.unlock(); m_mutex2.unlock(); return false; } //把收据从消息队列中取出的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; ++i) { bool result = outMsgLockPro(command); if (result == true) { std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl; //可以考虑进行命令(数据)处理 } else { std::cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << std::endl; } } std::cout << "END" << std::endl; } private: std::list<int>msgRecvQueue;//存储玩家发送过来的命令--消息队列 std::mutex m_mutex1;//创建互斥量 std::mutex m_mutex2; }; int main() { A myobj; std::thread m_outMsgThread(&A::outMsgRecvQueue, &myobj); std::thread m_inMsgThread(&A::inMsgRecvQueue, &myobj); m_outMsgThread.join(); m_inMsgThread.join(); getchar(); return 0; }
(2)死锁的一般解决方案
只要保证两个互斥量的上锁顺序一致就不会造成死锁。
对上面死锁的修正:
#include<iostream> #include<vector> #include<list> #include<thread> #include<mutex> class A { public: //把收到的消息(玩家命令)写入到一个队列的线程 void inMsgRecvQueue() { for (int i = 0; i < 100000; ++i) { m_mutex1.lock(); m_mutex2.lock(); msgRecvQueue.push_back(i);//假设数字i就是收到的命令 m_mutex2.unlock(); m_mutex1.unlock(); } } bool outMsgLockPro(int &command) { m_mutex1.lock(); m_mutex2.lock(); if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); msgRecvQueue.pop_front();//移除list首元素 m_mutex2.unlock(); m_mutex1.unlock(); return true; } m_mutex2.unlock(); m_mutex1.unlock(); return false; } //把收据从消息队列中取出的线程 void outMsgRecvQueue() { int command; for (int i = 0; i < 100000; ++i) { bool result = outMsgLockPro(command); if (result == true) { std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl; //可以考虑进行命令(数据)处理 } else { std::cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << std::endl; } } std::cout << "END" << std::endl; } private: std::list<int>msgRecvQueue;//存储玩家发送过来的命令--消息队列 std::mutex m_mutex1;//创建互斥量 std::mutex m_mutex2; }; int main() { A myobj; std::thread m_outMsgThread(&A::outMsgRecvQueue, &myobj); std::thread m_inMsgThread(&A::inMsgRecvQueue, &myobj); m_outMsgThread.join(); m_inMsgThread.join(); getchar(); return 0; }
(3)std::lock()函数模板
作用:一次锁住
两个或者两个以上的互斥量,且不存在再多个线程中,因为锁的顺序问题导致死锁的风险问题。
std::lock():如果互斥量中有一个没锁住,它就等待所有互斥量都锁住,才继续往下走。特点是:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,而另外一个没锁成功,则立即把已经锁住的解锁。
std::lock()的应用方式:
void inMsgRecvQueue() { for (int i = 0; i < 100000; ++i) { std::lock(m_mutex1, m_mutex2); msgRecvQueue.push_back(i);//假设数字i就是收到的命令 m_mutex2.unlock(); m_mutex1.unlock(); } }
(4)std::lock_guard()的std::adopt_lock参数
std::adopt_lock参数作用:表示此互斥量已经lock(),不需要在
std::lock_guard<std::mutex>构造函数里面再次对对象在此lock()。
void inMsgRecvQueue() { for (int i = 0; i < 100000; ++i) { std::lock(m_mutex1, m_mutex2);//防止死锁且自动lock std::lock_guard<std::mutex>lockGuard1(m_mutex1, std::adopt_lock);//自动解锁且不用再加锁(std::adopt_lock) std::lock_guard<std::mutex>lockGuard2(m_mutex2, std::adopt_lock); msgRecvQueue.push_back(i);//假设数字i就是收到的命令 } }
相关文章推荐
- 多线程程序退出内存句柄泄漏的问题以及解决方法
- 多线程——问题引出以及解决方法
- Servlet和Filter的url匹配以及url-pattern详解 及 filter 循环问题的解决
- MySQL死锁问题分析及解决方法实例详解
- 安装widows mysql 免安装版(zip) 步骤详解 以及遇到问题解决 以及忘记密码的修改方法
- MySQL死锁问题分析及解决方法实例详解
- 黑马程序员_多线程之同步问题的前期,以及安全问题的发现和解决
- 多线程多生产多消费问题以及解决
- 安装widows mysql 免安装版(zip) 步骤详解 以及遇到问题解决 以及忘记密码的修改方法
- spr 4000 ingmvc 属性放数据库中解决方法 以及 @Controller 中 使用@vlaue无法注入属性值问题详解
- 多线程编程:线程死锁的原因以及解决方法
- 如何解决多线程程序中的死锁问题(转)
- java中多线程的安全问题以及解决办法
- Jsoncpp使用详解以及链接问题解决
- PHP header函数设置http报文头示例详解以及解决http返回头中content-length与Transfer-Encoding: chunked的问题
- 安装widows mysql 免安装版(zip) 步骤详解 以及遇到问题解决 以及忘记密码的修改方法
- 安装widows mysql 免安装版(zip) 步骤详解 以及遇到问题解决 以及忘记密码的修改方法
- 安装widows mysql 免安装版(zip) 步骤详解 以及遇到问题解决 以及忘记密码的修改方法
- Linux系统网卡配置详解以及—常见问题的解决方法以及—硬盘移植后网卡的配置
- [置顶] MySQL 5.7主从复制从零开始设置及全面详解——实现多线程并行同步,解决主从复制延迟问题!