死锁

如果你将某个mutex上锁了,却一直不释放,另一个线程访问该锁保护的资源的时候,就会发生死锁,这种情况下使用lock_guard可以保证析构的时候能够释放锁,然而,当一个操作需要使用两个互斥元的时候,仅仅使用lock_guard并不能保证不会发生死锁,如下面的例子:

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
using namespace std;class LogFile {std::mutex _mu;std::mutex _mu2;ofstream f;
public:LogFile() {f.open("log.txt");}~LogFile() {f.close();}void shared_print(string msg, int id) {std::lock_guard<std::mutex> guard(_mu);std::lock_guard<std::mutex> guard2(_mu2);f << msg << id << endl;cout << msg << id << endl;}void shared_print2(string msg, int id) {std::lock_guard<std::mutex> guard(_mu2);std::lock_guard<std::mutex> guard2(_mu);f << msg << id << endl;cout << msg << id << endl;}
};void function_1(LogFile& log) {for(int i=0; i>-100; i--)log.shared_print2(string("From t1: "), i);
}int main()
{LogFile log;std::thread t1(function_1, std::ref(log));for(int i=0; i<100; i++)log.shared_print(string("From main: "), i);t1.join();return 0;
}

运行之后,你会发现程序会卡住,这就是发生死锁了。程序运行可能会发生类似下面的情况:

Thread A              Thread B
_mu.lock()          _mu2.lock()//死锁               //死锁
_mu2.lock()         _mu.lock()

解决办法有很多:

  1. 可以比较mutex的地址,每次都先锁地址小的,如:

    if(&_mu < &_mu2){_mu.lock();_mu2.unlock();
    }
    else {_mu2.lock();_mu.lock();
    }
    
  2. 使用层次锁,将互斥锁包装一下,给锁定义一个层次的属性,每次按层次由高到低的顺序上锁。

这两种办法其实都是严格规定上锁顺序,只不过实现方式不同。

c++标准库中提供了std::lock()函数,能够保证将多个互斥锁同时上锁,

std::lock(_mu, _mu2);

同时,lock_guard也需要做修改,因为互斥锁已经被上锁了,那么lock_guard构造的时候不应该上锁,只是需要在析构的时候释放锁就行了,使用std::adopt_lock表示无需上锁:

std::lock_guard<std::mutex> guard(_mu2, std::adopt_lock);
std::lock_guard<std::mutex> guard2(_mu, std::adopt_lock);

完整代码如下:

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
using namespace std;class LogFile {std::mutex _mu;std::mutex _mu2;ofstream f;
public:LogFile() {f.open("log.txt");}~LogFile() {f.close();}void shared_print(string msg, int id) {std::lock(_mu, _mu2);std::lock_guard<std::mutex> guard(_mu, std::adopt_lock);std::lock_guard<std::mutex> guard2(_mu2, std::adopt_lock);f << msg << id << endl;cout << msg << id << endl;}void shared_print2(string msg, int id) {std::lock(_mu, _mu2);std::lock_guard<std::mutex> guard(_mu2, std::adopt_lock);std::lock_guard<std::mutex> guard2(_mu, std::adopt_lock);f << msg << id << endl;cout << msg << id << endl;}
};void function_1(LogFile& log) {for(int i=0; i>-100; i--)log.shared_print2(string("From t1: "), i);
}int main()
{LogFile log;std::thread t1(function_1, std::ref(log));for(int i=0; i<100; i++)log.shared_print(string("From main: "), i);t1.join();return 0;
}

总结一下,对于避免死锁,有以下几点建议:

  1. 建议尽量同时只对一个互斥锁上锁。

    {std::lock_guard<std::mutex> guard(_mu2);//do somethingf << msg << id << endl;
    }
    {std::lock_guard<std::mutex> guard2(_mu);cout << msg << id << endl;
    }
    
  2. 不要在互斥锁保护的区域使用用户自定义的代码,因为用户的代码可能操作了其他的互斥锁。

    {std::lock_guard<std::mutex> guard(_mu2);user_function(); // never do this!!!f << msg << id << endl;
    }
    
  3. 如果想同时对多个互斥锁上锁,要使用std::lock()

  4. 给锁定义顺序(使用层次锁,或者比较地址等),每次以同样的顺序进行上锁。详细介绍可看C++并发编程实战。

参考

  1. C++并发编程实战
  2. C++ Threading #4: Deadlock

c++11 多线程编程(四)------ 死锁(Dead Lock)相关推荐

  1. C++11多线程编程(八)——死锁问题

    一.死锁现象 看到"死锁"二字,你是不是慌得不知所措.死锁,顾名思义就是这个锁死掉了,再也动不了了.那死锁是怎么产生的呢?当你对某个资源上锁后,却迟迟没有释放或者根本就无法释放,导 ...

  2. Linux与C++11多线程编程(学习笔记)

    多线程编程与资源同步 在Windows下,主线程退出后,子线程也会被关闭; 在Linux下,主线程退出后,系统不会关闭子线程,这样就产生了僵尸进程 3.2.1创建线程 Linux 线程的创建 #inc ...

  3. c++11多线程编程同步——使用条件变量condition variable

    简述 在多线程编程中,当多个线程之间需要进行某些同步机制时,如某个线程的执行需要另一个线程完成后才能进行,可以使用条件变量. c++11提供的 condition_variable 类是一个同步原语, ...

  4. Java - 死锁 Dead Lock 定位分析

    文章目录 Pre jstack Thread dump Dead Lock 分析 分析代码 解决 Pre JVM-11虚拟机性能监控与故障处理工具之[JDK的可视化工具-JConsole] jstac ...

  5. c++11 多线程编程(六)------条件变量(Condition Variable)

    互斥锁std::mutex是一种最常见的线程间同步的手段,但是在有些情况下不太高效. 假设想实现一个简单的消费者生产者模型,一个线程往队列中放入数据,一个线程往队列中取数据,取数据前需要判断一下队列中 ...

  6. c++11 多线程编程(三)------ 竞争和互斥锁

    竞争条件 并发代码中最常见的错误之一就是竞争条件(race condition).而其中最常见的就是数据竞争(data race),从整体上来看,所有线程之间共享数据的问题,都是修改数据导致的,如果所 ...

  7. c++11 多线程编程(一)------初始

    什么是并发 并发在生活中随处可见,边走路边说话,边听歌边写代码.计算机术语中的"并发",指的是在单个系统里同时执行多个独立的活动,而不是顺序的一个接一个的执行.对于单核CPU来说, ...

  8. 多线程编程 ----- 四种同步方法

    多线程,就是在一个进程里除主线程外创建其他的线程为进程工作,为什么要使用多线程?因为使用多线程在多数情况下都能提升运行速度.多线程主要的应用场景有 避免阻塞(异步调用) 比如你用一般模型的socket ...

  9. java安全编码指南之:死锁dead lock

    文章目录 简介 不同的加锁顺序 使用private类变量 使用相同的Order 释放掉已占有的锁 简介 java中为了保证共享数据的安全性,我们引入了锁的机制.有了锁就有可能产生死锁. 死锁的原因就是 ...

最新文章

  1. 编译-链接-运行-环境配置各种error汇总
  2. OSPF:MTU不一致导致的邻接关系问题
  3. 使用Selenium模拟浏览器,实现自动爬取数据
  4. python 对axis的理解
  5. OC 方法,继承,特殊方法
  6. gdpr合规性测试_使用生产数据在GDPR后世界进行测试
  7. TypeScript学习(八):数组的补充及内置对象说明
  8. 数据结构之线性表之顺序存储结构(3)
  9. 网络_检测公网端口是否开启
  10. Elasticsearch Index Template(索引模板)
  11. Webgoat学习笔记1
  12. 从本机复制文件到VM虚拟机出现卡死
  13. WiFiDisplay
  14. python no such file or directory_python No such file or Directory
  15. CSS基础的文字样式
  16. 计算机桌面文件删除不掉是怎么了,桌面上文件删不掉_桌面上的压缩文件为什么删除不了?...
  17. 艾可森 mysql,国足进世界杯有戏!巴西归化球员表决心:中国对我好,我必须努力...
  18. LeeCode1468. 计算税后工资
  19. LinkedList源码浅析
  20. 新手零基础快速入门Docker

热门文章

  1. 选出一个从零到五十之间的数,要求能被3整除且一位上的数为5
  2. 《阿甘正传》,看了很多遍,是否留意到这个镜头???
  3. Q123:PBRT-V3,各种形式的“光传播方程”的推导依据
  4. Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行——怎么将Unix/Mac系统下的文件转换到Windows系统下
  5. 计算机专业岗位细分及学习必备清单
  6. 如何选择大数据软件开发公司
  7. php curl 请求失败,PHP CURL库之GET、POST数据大小限制导致请求失败解决方案
  8. 有服主传送玩家指令_我的世界:不用指令能够到达边境之地吗?实验证明,根本就不可能...
  9. HTML使川锚标签,第1章HTML的基本标签祥解.ppt
  10. mysql 日志mixed模式_[MySQL binlog]彻底解析Mixed日志格式的binlog