《C++ Concurrency in Action》笔记8 死锁(2)避免死锁

死锁大多由lock引起,但是有时候没有执行lock操作仍然可以引起死锁。例如,两个线程互相jion导致的死锁;或者多个线程形成的一个循环join导致的死锁。避免死锁的方法可以归结为一句话:不要等待另一个线程,如果它此时正在等待你。下面是作者提供的如果识别和消除死锁的几个准则:

一、避免嵌套锁

首先的建议是,不要重复锁定。如果你已经锁定了就不要再次锁定。如果想同时对多个对象进行锁定操作,那么使用std::lock()函数。

二、当你已经保持一个锁状态时,避免调用用户提供的代码

很多时候你无法判断用户提供的代码究竟做了什么,他有可能做任何事,包括请求一个锁操作。如果你已经获取并保持了一个锁状态,此时你调用的用户代码如果再去请求一个锁定操作,那么久违反了第一条准则。如果你不能避免调用用户代码,那么你需要一个新的准则。

三、按固定顺序获取锁

如果你不得不请求多个锁,而且你无法使用std::lock()函数,那么此时你需要做的就是在每个线程中按固定的顺序去请求这些锁。但是有时这种方法也会出问题,例如3.1节中说的双向链表,一种可能的保护策略是为每个节点配置一个mutex,如果要删除一个节点,你必须锁定包括要删除的节点以及其前一个以及后一个节点在内的3个节点。如果想要遍历这个list,那么你需要在锁定当前节点的同时获取下一个节点,这样做的目的是为了保证下一个节点没有被修改,当获取到下一个节点后,之前的节点就可以解锁了。这种策略正常工作的前提是,不同的线程必须按同样的顺序锁定节点。如果两个线程按相反的方向遍历list,那么就会产生死锁。或者,如果一个线程试图删除一个节点,另一线程试图遍历节点,仍然会产生死锁。

一种解决方案就是规定一个遍历的顺序以避免死锁,当然这也付出了相应的代价:不允许反向遍历。类似的情况也会发生在其他数据结构中。

四、使用自定义的层级锁

一个层级锁规定只能按由高到低的顺序执行锁操作。可以为每个层级分配一个序号,并可在运行时检查和记录不同层级锁之间的操作顺序。下面列出一个层级锁的使用程序:

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();//将记录错误并引发异常
}

thread_a()函数按照正确的顺序执行锁操作,没有问题。但是thread_b()函数中的操作违背了原则,因此将导致异常。使用层级锁不太可能会导致死锁,因为在任意时刻你不能保持两个同级的锁,因此,之前所说的在链表中逆序遍历也会自动被避免。

实现一个层级锁不是很难的事情。为了让std::lock_guard<>使用,我们需要提供三个函数:lock()、unlock()、try_lock()。下面的程序给出一个简单的实现,其中没有直接使用try_lock()函数,try_lock的功能是在非阻塞的情况下试图锁定指定的mutex,如果成功返回true,失败返回false。

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;
	}
};

 thread_local 用来声明一个变量为线程本地变量,只用于声明全局变量或者代码块中的static变量。每个线程中拥有一个自己的变量副本,不同线程之间的副本没有关联。

 ULONG_MAX代表最大的无符号long型的最大值。

它的特点是:只在每个线程中起作用,不同线程之间没有关联。在同一个线程中,每次只能锁定比上一次等级低的mutex,只要锁定过一个等级后,那么即使重新定义一个hierarchical_mutex对象,也只能锁定比之前等级低的mutex。这当然也不允许重复锁定同一个等级的mutex,就算你释放了再重新锁定也不行。

综上所诉,死锁可能由任何触发等待的同步操作引发,应该小心谨慎。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值