《C++ Concurrency in Action》笔记8 死锁(2)避免死锁

来源:互联网 发布:steam一直无法连接网络 编辑:程序博客网 时间:2024/06/05 06:32

死锁大多由lock引起,但是有时候没有执行lock操作仍然可以引起死锁。例如,两个线程互相jion导致的死锁;或者多个线程形成的一个循环join导致的死锁。避免死锁的方法可以归结为一句话:不要等待另一个线程,如果它此时正在等待你。下面是作者提供的如果识别和消除死锁的几个准则:

一、避免嵌套锁

首先的建议是,不要重复锁定。如果你已经锁定了就不要再次锁定。如果想同时对多个对象进行锁定操作,那么使用std::lock()函数。

二、当你已经保持一个锁状态时,避免调用用户提供的代码

很多时候你无法判断用户提供的代码究竟做了什么,他有可能做任何事,包括请求一个锁操作。如果你已经获取并保持了一个锁状态,此时你调用的用户代码如果再去请求一个锁定操作,那么久违反了第一条准则。如果你不能避免调用用户代码,那么你需要一个新的准则。

三、按固定顺序获取锁

如果你不得不请求多个锁,而且你无法使用std::lock()函数,那么此时你需要做的就是在每个线程中按固定的顺序去请求这些锁。但是有时这种方法也会出问题,例如3.1节中说的双向链表,一种可能的保护策略是为每个节点配置一个mutex,如果要删除一个节点,你必须锁定包括要删除的节点以及其前一个以及后一个节点在内的3个节点。如果想要遍历这个list,那么你需要在锁定当前节点的同时获取下一个节点,这样做的目的是为了保证下一个节点没有被修改,当获取到下一个节点后,之前的节点就可以解锁了。这种策略正常工作的前提是,不同的线程必须按同样的顺序锁定节点。如果两个线程按相反的方向遍历list,那么就会产生死锁。或者,如果一个线程试图删除一个节点,另一线程试图遍历节点,仍然会产生死锁。

一种解决方案就是规定一个遍历的顺序以避免死锁,当然这也付出了相应的代价:不允许反向遍历。类似的情况也会发生在其他数据结构中。

四、使用自定义的层级锁

一个层级锁规定只能按由高到低的顺序执行锁操作。可以为每个层级分配一个序号,并可在运行时检查和记录不同层级锁之间的操作顺序。下面列出一个层级锁的使用程序:

hierarchical_mutex high_level_mutex(10000);hierarchical_mutex low_level_mutex(5000);int do_low_level_stuff();int low_level_func(){std::lock_guard<hierarchical_mutex> lk(low_level_mutex);return do_low_level_stuff();}void high_level_stuff(int some_param);void high_level_func(){std::lock_guard<hierarchical_mutex> lk(high_level_mutex);high_level_stuff(low_level_func());}void thread_a(){high_level_func();}hierarchical_mutex other_mutex(100);void do_other_stuff();void other_stuff(){high_level_func();do_other_stuff();}void thread_b(){std::lock_guard<hierarchical_mutex> lk(other_mutex);other_stuff();//将记录错误并引发异常}

thread_a()函数按照正确的顺序执行锁操作,没有问题。但是thread_b()函数中的操作违背了原则,因此将导致异常。使用层级锁不太可能会导致死锁,因为在任意时刻你不能保持两个同级的锁,因此,之前所说的在链表中逆序遍历也会自动被避免。

实现一个层级锁不是很难的事情。为了让std::lock_guard<>使用,我们需要提供三个函数:lock()、unlock()、try_lock()。下面的程序给出一个简单的实现,其中没有直接使用try_lock()函数,try_lock的功能是在非阻塞的情况下试图锁定指定的mutex,如果成功返回true,失败返回false。

class hierarchical_mutex{std::mutex internal_mutex;unsigned long const hierarchy_value;unsigned long previous_hierarchy_value;static thread_local unsigned long this_thread_hierarchy_value;void check_for_hierarchy_violation(){if (this_thread_hierarchy_value <= hierarchy_value){throw std::logic_error(“mutex hierarchy violated”);}}void update_hierarchy_value(){previous_hierarchy_value = this_thread_hierarchy_value;this_thread_hierarchy_value = hierarchy_value;}public:explicit hierarchical_mutex(unsigned long value) :hierarchy_value(value),previous_hierarchy_value(0){}void lock(){check_for_hierarchy_violation();internal_mutex.lock();update_hierarchy_value();}void unlock(){this_thread_hierarchy_value = previous_hierarchy_value;internal_mutex.unlock();}bool try_lock(){check_for_hierarchy_violation();if (!internal_mutex.try_lock())return false;update_hierarchy_value();return true;}};

 thread_local 用来声明一个变量为线程本地变量,只用于声明全局变量或者代码块中的static变量。每个线程中拥有一个自己的变量副本,不同线程之间的副本没有关联。

 ULONG_MAX代表最大的无符号long型的最大值。

它的特点是:只在每个线程中起作用,不同线程之间没有关联。在同一个线程中,每次只能锁定比上一次等级低的mutex,只要锁定过一个等级后,那么即使重新定义一个hierarchical_mutex对象,也只能锁定比之前等级低的mutex。这当然也不允许重复锁定同一个等级的mutex,就算你释放了再重新锁定也不行。

综上所诉,死锁可能由任何触发等待的同步操作引发,应该小心谨慎。

原创粉丝点击