前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >UNIX(多线程):15---死锁(Dead Lock)

UNIX(多线程):15---死锁(Dead Lock)

作者头像
用户3479834
发布2021-02-03 12:31:04
3940
发布2021-02-03 12:31:04
举报
文章被收录于专栏:游戏开发司机

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

代码语言:javascript
复制
#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

代码语言:javascript
复制
_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);

代码语言:javascript
复制

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

代码语言:javascript
复制
std::lock_guard<std::mutex> guard(_mu2, std::adopt_lock);std::lock_guard<std::mutex> guard2(_mu, std::adopt_lock);

完整代码如下:

代码语言:javascript
复制
#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 something f << 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. 给锁定义顺序(使用层次锁,或者比较地址等),每次以同样的顺序进行上锁。如果喜欢此文章,请分享给大家吧。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 游戏开发司机 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 如果你将某个mutex上锁了,却一直不释放,另一个线程访问该锁保护的资源的时候,就会发生死锁,这种情况下使用lock_guard可以保证析构的时候能够释放锁,然而,当一个操作需要使用两个互斥元的时候,仅仅使用lock_guard并不能保证不会发生死锁,如下面的例子:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档