[其他] 可能导致死锁的几个场景

场景一:多线程 + 多锁

这种场景最容易常见和容易理解,如果同时存在多个线程和多个锁,而每个线程都有机会操作所有的锁,那么这就可能导致死锁。

解决办法一:始终按照一致的顺序对多个锁进行加锁,比如有两个锁 A 和 B,那么任何线程在对 A 和 B加锁时都要按照 “先A后B” 或者 “先B后A” 的顺序进行即可。倘若实在无法确保顺序,那么可以使用c++ 11提供的 std::lock 进行同步加锁,此组件可以保证传入的多个锁可以在不死锁的情况下依次锁住。

解决方法二:使用trylock系列函数,当发现加锁失败时,立刻进行一次释放所有锁的动作,然后再次尝试刚才的加锁。

msdn:two threads try to acquire the same two locks but acquire them in a different order, you may get a deadlock. 

 

场景二:线程对同一个锁反复加锁

如果单线程对同一个锁加锁两次,那么这会导致死锁,这种情况在 使用自释放锁时经常出现,比如 c++ 11 的 lock_guard<mutex> ,如果在 lock_guard 还没释放时再次对同一个锁使用 lock_guard<mutex> ,则会立刻导致死锁。

解决办法:使用递归锁,递归锁会记录上一次对自己加锁的线程id,如果发现是当前执行线程和上一次加锁线程是同一个则不再做加锁动作。

#include <iostream>
#include <Windows.h>
#include <mutex>
using namespace std;

void CriticalSection_2();

mutex m1;
recursive_mutex m2;

void CriticalSection_1()
{
	unique_lock<recursive_mutex> lockguard(m2);				//正常
	//lock_guard<recursive_mutex> lockguard(m2);			//正常
	//unique_lock<mutex> lockguard(m1);						//崩溃
	//lock_guard<mutex> lockguard(m1);						//崩溃
	CriticalSection_2();
}

void CriticalSection_2()
{
	unique_lock<recursive_mutex> lockguard(m2);				//正常
	//lock_guard<recursive_mutex> lockguard(m2);			//正常
	//unique_lock<mutex> lockguard(m1);						//崩溃
	//lock_guard<mutex> lockguard(m1);						//崩溃
}


int main()
{
	CriticalSection_1();

	std::cout << "Hello World!\n";
	while (1) Sleep(1000);
}

 

场景三:优先级翻转

如果是非实时操作系统,而且进行了优先级划分,那么低优先级的线程可能会持有高优先级线程所需要的锁,又因为是非实时操作系统,所以不能保证所有线程都会在一定地时间段内一定会被调用,所以低优先级线程可能很长时间都不会被调度,进而无法得到CPU执行代码,这边导致释放锁的语句长时间无法得到执行,所以高优先级的线程因为等待低优先级线程释放锁而被阻塞住,高优先级线程又必须等待低优先级线程被调度后释放锁才能继续执行,如果高优先级线程是主线程,那么这个程序很可能就会卡死无响应(Windows系统中那些无响应一段时间后又能恢复的进程大多是这个原因)。如果此时高优先级线程还持有了低优先级线程需要的锁,那么这一定会导致死锁。

解决办法:如非必须,不要设置线程优先级,如果必须,那么要仔细规划,并增加一些超时机制,具体解决办法类比场景一的解决办法

msdn:a high-priority thread spins in a loop while waiting for a low-priority thread to release a lock. Finally, if a deferred procedure call or interrupt service routine tries to acquire a lock, you may get a deadlock.

 

避免死锁的几个原则:

避免死锁的手段有很多,可以有N多种实现方式,但是都离不开如下几个大原则:

  • 如果当前线程已经持有锁了,那么再申请锁的时候要谨慎,如果是一次性拿多个锁,那么 std::lock 可以保证安全性
  • 脱离当前模块时最好释放手上的锁,比如通过回调函数进入其他代码模块时,因为我们无法保证其他模块会调用什么代码,很可能他们会调用一个对当前持有的锁再次加锁的API,这变会导致死锁。(这里也看得出递归锁的重要性)
  • 如果不是一次性拿多个锁,而是分步骤地拿,比如拿了锁,然后执行一段代码,再拿第二个锁,这种情况下,一定要保证所有线程对于这些锁的处理顺序是一样的。这其实很难,因为我们很难通过有效的代码手段进行限制(这里说的是很难,不是不行,因为保证锁的顺序肯定需要再引入复杂的数据结构,甚至是引入新的锁,这极大地增加了代码复杂度),因为这些锁是分布在业务代码中的,我们无法预知何时会调用,所以只能在编程的时候注意一点。
  • 引入锁的等级制度,这就是上一条中说的很难的手段,我们首先要对所有线程进行归纳分类,然后统计所有锁,并对这些锁按照一定策略划分等级,并给锁安排一个数据结构用来存放他们的等级。接下来只要按照一个原则进行实现 —— “如果当前持有低等级锁,那么不能再对高等级锁加锁”。

 

等级锁方案Demo:

class hierarchical_mutex
{
	std::mutex internal_mutex;
	unsigned long const hierarchy_value;
	unsigned long previous_hierarchy_value;
	static thread_local unsigned long this_thread_hierarchy_value;
	void check_for_hierarchy_violation()
	{
		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) :
		hierarchy_value(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;
	}
};

//----------------------------------------------------------

hierarchical_mutex high_level_mutex(10000);
hierarchical_mutex low_level_mutex(5000);
int do_low_level_stuff();
int low_level_func()
{
	std::lock_guard<hierarchical_mutex> lk(low_level_mutex);
	return do_low_level_stuff();
}
void high_level_stuff(int some_param);
void high_level_func()
{
	std::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_level_func();
	do_other_stuff();
}
void thread_b()
{
	std::lock_guard<hierarchical_mutex> lk(other_mutex);
	other_stuff();
}

 

 

Tips

1)对mutex解锁永远不会抛出异常,反之对其加锁可能会抛出异常

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值