在使用互斥量时可能会造成死锁。造成死锁,必须满足四个条件:
1、互斥使用(资源独占)
一个资源每次只能给一个进程使用
2、不可强占(不可剥夺)
资源申请者不能强行的从资源占有者手中夺取资源,资源只能由占有者自愿释放
3、请求和保持(部分分配,占有申请)
一个进程在申请新的资源的同时保持对原有资源的占有(只有这样才是动态申请,动态分配)
4、循环等待
存在一个进程等待队列
{P1 , P2 , … , Pn},
其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路
在使用互斥量时,有2中情况容易发生死锁。
1、两个(或多个线程)各自持有一个互斥量,且还需要一个(或多个)互斥量,而这个互斥量被对方已经上锁。
一个最简单情况:线程A和线程B,都需要两个锁,但是每个线程却持有一个。
还有一种情况就是加锁顺序不一致。例如要加两个锁m1和m2,一个函数先对m1加锁再对m2加锁,另一个函数相反,这就容易发生死锁。
一个例子: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);
//休眠,使死锁更容易发生
sleep(1);
std::lock_guard<std::mutex> guard2(m2);
}
void fun2()
{
std::lock_guard<std::mutex> guard1(m2);
//休眠,使死锁更容易发生
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多线程服务器编程》中看到过一个方法:给多个互斥量加锁时,按照互斥量地址高低来加锁,这样就确保了加锁顺序。
避免死锁的方法
#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);
}
};
转移锁的控制权
可以转移(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();
}
以适当的粒度加锁
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;
}
};