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()。
  这个简单的死循环可以发生在任何地方,如果一个线程等待另一个线程执行,而另一个线程又在等待这个线程。除了两个线程,多个线程之间同样也可能出现死锁。
  一个原则是,如果另外一个线程可能会依赖当前线程,则不要再让当前线程依赖那个线程了。
  
  


C++11线程指南(七)--死锁,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。