C++11线程指南(8)--死锁

来源:互联网 发布:国产芯片 知乎 编辑:程序博客网 时间:2024/04/28 22:56

1. 死锁

  当多个mutex存在的时候,可能就会产生死锁。
  避免死锁的一个最通用的方法是,总是按照相同的顺序来锁定两个mutexes, 即总是先于mutex B之前lock mutex A,这样就不会有死锁的可能。有时,这种方法很简单实用,当这些mutexes用于不同的目标。但是,当mutexes用于包含相同类的一个实例时,就不是那么容易了。
  例如,如下面程序所示,相同类的两个实例之间交互数据。为了保证数据交互不换并发影响,两个实例都使用mutex进行保护。但是当mutex被嵌套的调用时,就形成了死锁。
  然而,C++标准库中的std::lock可以解决死锁,它能同时的lock多个mutex,而不会形成死锁。

#include <mutex>class Dummy {};void swap(Dummy& lhs,Dummy& rhs);class A{private:Dummy myObj;std::mutex mu;public:A(Dummy const& obj):myObj(obj){}friend void swap(A& lhs, A& rhs){        //需要先检测这两个实例是否相同。因为如果在一个已经被获取到的std::mutex上,        //试图再次获取锁,这会导致未知的行为。if(&lhs==&rhs)    return;// 调用std::lock()来锁定这两个mutexes。std::lock(lhs.mu,rhs.mu);// 构造两个std::lock_guard实例,每个对应一个mutex。std::lock_guard<std::mutex> lock_a(lhs.mu,std::adopt_lock);std::lock_guard<std::mutex> lock_b(rhs.mu,std::adopt_lock);swap(lhs.myObj, rhs.myObj);}};
  swap函数开始会检测参数是否相同,因为重复去lock一个已经被锁定了的std::mutex会导致未知行为。如果需要支持重复的lock, 可以采用std::recursive_mutex。
  然后,调用std::lock()来锁定两个mutexes,接着构造了两个std::lock_guard实例。
  参数std::adopt_lock用来告诉std::lock_guard,传入的mutex已经被lock了,它们仅能拥有mutex上面已经存在的锁的使用权,而不能在构造函数中再次lock the mutex.
  这就确保了mutexes在函数退出时,或抛出异常时,能被准确的unlock. 另外,如果std::lock成功地在一个mutex上lock, 但是lock另一个mutex时抛出了异常,前一个被锁的mutex会被自动释放。std::lock提供了all-or-nothing机制。
  尽管std::lock能帮组我们需要一起获取多个mutex时,避免死锁。但是,分别获取多个mutex时,却无能为力。这种情况下,就需要开发人员自己来避免出现死锁了。这不是容易的事情,因为死锁是多线程代码中最可能出现的问题。不过,还是存在一些规则来避免死锁。

2. 避免死锁

  尽管锁是出现在死锁中的最常见的要素,但是死锁并不只是会占用锁。我们可以在两个线程之间,不使用锁来创建一个死锁,例如,两个std:thread object相互调用join()。
  这个简单的死循环可以发生在任何地方,如果一个线程等待另一个线程执行,而另一个线程又在等待这个线程。除了两个线程,多个线程之间同样也可能出现死锁。
  一个原则是,如果另外一个线程可能会依赖当前线程,则不要再让当前线程依赖那个线程了。

0 0
原创粉丝点击