C++11线程指南(七)--死锁
1. 死锁
在多个mutex存在的时候,可能就会产生死锁。避免死锁的一个最通用的方法是,总是按照相同的顺序来lock the two mutexes, 即总是先于mutex B之前lock mutex A,这样就不会有死锁的可能。有时,这种方法很简单实用,当这些mutexes用于不同的目标。但是,当mutexes用于包含相同类的一个实例时,就不是那么容易了。
例如,如下面程序所示,相同类的两个实例之间交互数据。为了保证数据交互不换并发影响,两个实例都使用mutex进行保护。但是当mutex被嵌套的调用时,就形成了死锁。
然而,C++标准库中的std::lock可以解决死锁,它能同时的lock多个mutex,而不会形成死锁。
#include <mutex> using namespace std; 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) { // the arguments are checked to ensure they are different instances, // because attempting to acquire a lock on a std::mutex // when we already hold it is undefined behavior. if(&lhs==&rhs) return; // the call to std::lock() locks the two mutexes std::lock(lhs.mu,rhs.mu); // two std::lock_guard instances are constructed one for each 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()。这个简单的死循环可以发生在任何地方,如果一个线程等待另一个线程执行,而另一个线程又在等待这个线程。除了两个线程,多个线程之间同样也可能出现死锁。
一个原则是,如果另外一个线程可能会依赖当前线程,则不要再让当前线程依赖那个线程了。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。