避免死锁的一些注意事项
1. 避免嵌套锁, 如果每个线程都只占有一个锁, 则可以很大程度上避免死锁。
其死锁的情况是, 线程 1 依次获得 A 对象和 B 对象的锁, 然后决定等另一个线程的信号再继续, 从而先释放了 B 对象的的锁。
可是线程 2 需要同时拥有对象 A 和对象 B 的锁才能向线程 1 发信号。
从而导致, 线程 2 因无法获得对象 A 上的锁, 因而阻塞, 等待线程 1 释放对象 A 上的锁。 而同时线程 1 也一直阻塞, 等待线程 2 的信号, 因此不会释放对象 A 的锁。
2. 用固定的顺序获取锁
如果非要同时拥有多个锁, 同时无法拥有单个的锁, 那么最好的处理方式是以固定的顺序得到他们。典型的情况是在一个链表的结构中, 如果要取得节点 B 的锁, 那么需要先取得节点 A 的锁, 接着取得节点 C 的锁。
3. 设置优先级
当多个锁存在时, 设立优先级是个好主意,这样可以避免低优先级的 mutex 会先于高优先级的拥有锁。例如以下代码:
hierarchical_mutex high_level_mutex(10000);
hierarchical_mutex low_level_mutex(5000);
int do_low_level_stuff();
int low_level_func()
{
lock_guard<hierarchical_mutex> lk(low_level_mutex);
return do_low_level_stuff();
}
void high_level_stuff(int some_param);
void high_level_func()
{
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_levek_func();
do_other_stuff();
}
void thread_b()
{
lock_guard<hierarchical_mutex> lk(other_mutex);
other_stuff();
}
其中, thread_a 就会成功, 而 therad_b 就会运行出错。
为啥呢?
因为它首先会拥有 other_mutex 的锁,other_mutex 的优先级只有 100, 却在 high_level_mutex 前得到锁, 这违反了优先级, 所以 hierarchical_mutex 会报错并抛出一个异常, 然后退出程序。
上文中提到的 hierarchical_mutex 并不是标准库的一部分, 但是可以很容易的实现:
class hierarchical_mutex
{
mutex internal_mutex;
unsigned long const hierarchy_value;
unsigned previous_hierarchy_value;
static thread_local unsigned long this_thread_hierarchy_value;
// second mutex must now be less than
//that of the mutex already held
//in order for the check to pass
void check_for_hierarcy_violation()
{
if(this_thread_hierarchy_value <= hierarchy_value){
throw std::logic_error("mutex hierarchical 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_hierarcy_violation();
internal_mutex.lock();
update_hierarchy_value();
}
void unlock()
{
this_thread_hierarchy_value = previous_hierarchy_value;
internal_mutex.unlock();
}
bool try_lock()
{
check_for_hierarcy_violation();
if(!internal_mutex.try_lock()){
return false;
}
update_hierarchy_value();
return true;
}
};
// initialized to the maximum value,
// so initially any mutex can be locked
thread_local unsigned long
hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX);