假设有两个线程,在执行某些操作时,都需要锁定一对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_local void 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);
版权声明:本文为博主原创文章,未经博主允许不得转载。