在使用相互排斥量时可能会造成死锁。造成死锁。必须满足四个条件:
1、相互排斥使用(资源独占)
一个资源每次仅仅能给一个进程使用
2、不可强占(不可剥夺)
资源申请者不能强行的从资源占有者手中夺取资源。资源仅仅能由占有者自愿释放
3、请求和保持(部分分配,占有申请)
一个进程在申请新的资源的同一时候保持对原有资源的占有(仅仅有这样才是动态申请,动态分配)
4、循环等待
存在一个进程等待队列
{P1 , P2 , … , Pn},
当中P1等待P2占有的资源。P2等待P3占有的资源,…。Pn等待P1占有的资源,形成一个进程等待环路
在使用相互排斥量时。有2中情况easy发生死锁。
1、两个(或多个线程)各自持有一个相互排斥量。且还须要一个(或多个)相互排斥量。而这个相互排斥量被对方已经上锁。
一个最简单情况:线程A和线程B。都须要两个锁,可是每一个线程却持有一个。
另一种情况就是加锁顺序不一致。
比如要加两个锁m1和m2。一个函数先对m1加锁再对m2加锁,另一个函数相反,这就easy发生死锁。
一个样例:https://github.com/KangRoger/CppConcurrencyInAction/blob/master/DeadLocktest1.cpp
#include<thread> #include<mutex> #include<unistd.h> class Test { private: std::mutex m1; std::mutex m2; public: void fun1() { std::lock_guard<std::mutex> guard1(m1); //休眠,使死锁更easy发生 sleep(1); std::lock_guard<std::mutex> guard2(m2); } void fun2() { std::lock_guard<std::mutex> guard1(m2); //休眠,使死锁更easy发生 sleep(1); std::lock_guard<std::mutex> guard2(m1); } }; void fun1(Test *p) { p->fun1(); } void fun2(Test *p) { p->fun2(); } int main() { Test t; std::thread A(fun1, &t); std::thread B(fun2, &t); A.join(); B.join(); return 0; }
2、对同一个相互排斥量两个加锁(相互排斥量是非递归)
一个样例:https://github.com/KangRoger/CppConcurrencyInAction/blob/master/DeadLocktest2.cpp
#include<thread> #include<mutex> #include<iostream> class Test { private: std::mutex m1; public: void fun1() { std::lock_guard<std::mutex> guard1(m1); fun2(); } void fun2() { std::lock_guard<std::mutex> guard1(m1); } }; void fun(Test *p) { p->fun1(); std::cout << "fun1" << std::endl; } int main() { Test t; std::thread A(fun, &t); A.join(); return 0; }
在使用两个(以上)相互排斥量时。确保加锁顺序同样就不会出现死锁。
有时候确定加锁顺序也会导致死锁。比如。两个锁来保护一个类的两个实例。一个函数实现交换这两个实例(函数第一个參数先加锁,第二个參数后加锁),当交换的两个实例是同一个实例时也会发生死锁。
在C++标准库中,有方法来解决问题:使用std::lock函数。它能一次性格两个以上相互排斥量上锁。而且确保不会发生死锁。
#include<mutex> class some_big_object { }; void swap(some_big_object& lhs, some_big_object& ths); class X { private: some_big_object some_detail; std::mutex m; public: X(some_big_object const& sd) :some_detail(sd){} friend void swap(X& lhs, X& rhs) { if (&lhs == rhs) return; std::lock(lhs.m, rhs.m);//同一时候给两个相互排斥量上锁 //以下仅仅是转移相互排斥量控制权。并没有给相互排斥量上锁 //为了确保超出作用域是,lock_guard给相互排斥量解锁 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); } };
当std::lock给多个相互排斥量加锁时,假设当中一个加锁失败,那么会抛出异常,而且已经加锁的相互排斥量也会自己主动释放锁。
在《Linux多线程server编程》中看到过一个方法:给多个相互排斥量加锁时。依照相互排斥量地址高低来加锁,这样就确保了加锁顺序。
避免死锁的方法
假设使用多个锁,使用std::lock。
区分每一个层次中使用的锁,当一个线程已经持有更低层次的锁时,不同意使用高层次的锁。能够在程序执行时给不同的锁加上层次号,记录每一个线程持有的锁。
#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);
使用std::unique_lock灵活上锁
class X { private: some_big_object some_detail; std::mutex m; public: X(some_big_object const& sd) :some_detail(sd){} friend void swap(X& lhs, X& rhs) { if (&rhs == &lhs) return; //以下是先占有锁,參数std::defer_lock表明先占有锁 std::unique_lock<std::mutex> lock_a(lhs.m, std::defer_lock); std::unique_lock<std::mutex> lock_a(rhs.m, std::defer_lock); std::lock(lock_a, lock_b); swap(lhs.some_detail, rhs.some_detail); } };
转移锁的控制权
std::unique_lock
能够转移(moveable)。可是不能够拷贝。
一种使用方法是在一个函数给一个相互排斥量上锁,然后把锁的控制权交给还有一个函数。
std::unique_lock<std::mutex> get_lock()//上锁。然后转移锁的控制权 { extern std::mutex some_mutex;//引用外部相互排斥量 std::unique_lock<std::mutex> lk(some_mutex); prepare_data(); return lk; } void process_data() { std::unique_lock<std::mutex> lk(get_lock()); do_something(); }
以适当的粒度加锁
在涉及到I/O操作的函数中,尽量不要使用锁。由于I/O操作非常耗时。
std::unique_lock()能够在不使用共享数据时解锁,在须要使用时再上锁。
void get_and_process_data() { std::unique_lock<std::mutex> my_lock(the_mutex);//上锁 some_class data_to_process=get_next_data_chunk(); my_lock.unlock();//解锁 result_type result=process(data_to_process); my_lock.lock(); write_result(data_to_process,result); }
class Y { private: int some_detail; mutable std::mutex m; int get_detail() const { std::lock_guard<std::mutex> lock_a(m); return some_detail; } public: Y(int sd):some_detail(sd){} friend bool operator==(Y const& lhs, Y const& rhs) { if(&lhs==&rhs) return true; int const lhs_value=lhs.get_detail(); int const rhs_value=rhs.get_detail(); return lhs_value==rhs_value; } };