[C++11 并发编程] 07 - Mutex 死锁

来源:互联网 发布:python 金融数据接口 编辑:程序博客网 时间:2024/05/21 22:23

假设有两个线程,在执行某些操作时,都需要锁定一对mutex,线程A锁定了mutex A,而线程B锁定了额mutex B,它们都在等待对方释放另一个mutex,这就会导致这两个线程都无法继续执行。这种情况就是死锁。

避免死锁最简单的方法是总是以相同的顺序对两个mutex进行锁定,比如总是在锁定mutex B之前锁定mutex A,就永远都不会死锁。

假设有一个操作要交换同一个类的两个实例的内容,为了交换操作不被并发修改影响,我们需要锁定这两个实例内部的mutex。但是,如果选定一个固定的顺序来锁定mutex(线锁定第一个参数指定对象的mutex,再锁定第二个参数指定对象的mutex)恰恰适得其反,会导致死锁。

针对这种情况,我们需要使用C++11标准库的std::lock操作来解决这个问题。lock函数可以接受两个或者多个mutex以避免死锁。

示例如下:

#include <mutex>class some_big_object{};void swap(some_big_object& lhs,some_big_object& rhs){}class X{private:    some_big_object some_detail;    mutable std::mutex m;public:    X(some_big_object const& sd):some_detail(sd){}        friend void swap(X& lhs, X& rhs)    {        // 判断两个实例是否相同,如果相同则直接退出        // 因为锁定已经被本线程锁定的mutex的结果是不确定的        if(&lhs==&rhs)            return;        // 锁定两个mutex        std::lock(lhs.m,rhs.m);        // 创建两个lock_guard实例,分别给它们传入一个mutex对象        // std::adopt_lock参数表明所传入的mutex已经被锁定了,它们只需要接受已锁定mutex的所有权        // 而不需要在它的构造函数中尝试锁定这个传入的mutex        std::lock_guard<std::mutex> lock_a(lhs.m,std::adopt_lock);        std::lock_guard<std::mutex> lock_b(rhs.m,std::adopt_lock);        swap(lhs.some_detail,rhs.some_detail);    }};int main(){}
使用std::lock_guard确保在程序抛出异常对出时,也能正确的对锁定的mutex进行解锁。

std::lock操作可以保证在成功锁定第一个mutex后,如果在尝试锁定第二个mutex时发生异常,第一个mutex会被自动解锁。std::lock操作只能在锁定多个mutex时帮助我们避免死锁,如果这些mutex是一个个被单独锁定的,就需要我们自己在实现的时候保证程序不会发生死锁。接下来我们看看,避免死锁的几个基本方法:

1. 避免嵌套锁:

    如果每个线程都只能锁定一个mutex,则不会发生死锁。如果要使用多个锁,就使用std::lock。

2. 避免在锁定了一个mutex后调用用户提供的代码:

    我们无法保证用户代码做了什么,其很有可能锁定其它的mutex而导致死锁。

3. 以固定的顺序锁定mutex:

    如果确实需要锁定多个mutex,而这些mutex可以被同时锁定,使用前面我们讲到std::lock方法来保证锁定mutex的顺序而避免死锁。如果这些mutex只能分别被锁定,则需要在实现时保证锁定mutex的顺序是固定的,比如总是在锁定B之前锁定A,在锁定C之前锁定B。

4. 使用分层锁:

    把程序分成几个层次。区分每个层次中使用的锁,当一个线程已经持有更低层次的锁时,不允许使用高层次的锁。可以在程序运行时给不同的锁加上层次号,记录每个线程持有的锁。

#include <stdexcept>#include<thread>#include<mutex>class hierarchical_mutex//实现mutex的三个接口lock,unlock,trylock{std::mutex internal_mutex;unsigned long const hierarchy_value;//记录mutex所在层次unsigned long previous_hierarchy_value;//记录上一个mutex的层次,解锁时恢复线程的层次//thread_local每一个线程都有自己的该全局变量的实例(instance)static thread_local unsigned long this_thread_hierarchy_value;//线程所在层次,是thread_localvoid check_for_hierarchy_violation(){//线程所在层次要大于当前的mutex的层次,否则抛出异常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) :hierarchical_mutex(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 unsigned long hierarchical_mutex::this_thread_hierarchy_value(ULLONG_MAX);


1 0