线程死锁一引起的系列思考

梦想游戏人
目录:
现代C++

发生死锁必须具备以下的4个条件

1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

:资源是互斥的

2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

:如果只请求一个资源 那么也不会发现

3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

:所有权

4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

:比如操作系统课老师举得例子 几个人吃饭,拿筷子问题 ,最后都会没拿到2只筷子而都吃不了饭

死锁例子

 static  std::recursive_mutex _mutex1;
static  std::recursive_mutex _mutex2;
int hp;

 int main(int argc, char *argv[])
{
  thread t ( []()
  {  
    _mutex1.lock();
    Sleep(12);
    _mutex2.lock();
   cout << "t:"<< hp << endl;
 
    _mutex1.unlock();
   _mutex2.unlock();
  });
  t.detach();
 
  _mutex2.lock();
  Sleep(12);
  _mutex1.lock();
  cout << "main:" << hp << endl;
 

  _mutex1.unlock();
  _mutex2.unlock();
 system("pause");
 return 0;
}

mutex1 mutex2 为2个资源,造成了环路等待条件 导致线程死锁

递归问题:递归加锁了 如

void doWith()
{
 _mutex1.lock();
 cout << "doWith:" << hp << endl;
  _mutex1.unlock();

} 
  int main(int argc, char *argv[])
 {
 
  thread t ( [=]()
  {  
    _mutex1.lock();
 
   cout << "t:"<< hp << endl;
   doWith();
     _mutex1.unlock();
 
  });
  t.detach();
  system("pause");
 return 0;
}

mutex被递归加锁了   而 std::mutex 非递归锁,子程序和 匿名函数  位于同一个线程空间     就又会造成 死锁,因为子程序在等待 匿名函数的解锁,而进入死锁

这时候可用 std::recursive_mutex  递归锁 替代std::mutex  或者自己实现一个递归锁

原理大致是 如果 和上次成功lock的操作的线程 不在同一线程 就真正的 lock 否则仅仅是引用计数加1

 以下是我的一个实现

class Re_mutex
{
public:
 void lock()
 {
  cout << "lock id "<<this_thread::get_id() << endl;
  if (this_thread::get_id() == _last_lock_id &&_islock == true)
  {
   ++count;
  }
  else
  {
   _last_lock_id = this_thread::get_id();
   _islock = true;
   _mutex.lock();
  }
 }

 void unlock()
 {
  --count;
  if (count == 0 && _islock)
  {
   _mutex.unlock();
  }
 }
private:
 bool _islock = false;
 mutex _mutex;
 int count = 0;
 thread::id _last_lock_id;
};
static  Re_mutex _mutex1;
//static  std::recursive_mutex _mutex2;
int hp;
void doWith()
{
 _mutex1.lock();
 cout << "doWith:" << hp << endl;
 _mutex1.unlock();

}

 int main(int argc, char *argv[])
 {
 
  thread t ( [=]()
  {  
    _mutex1.lock();
 
   cout << "t:"<< hp << endl;
   doWith();
    _mutex1.unlock();
 
  });
  t.detach();
 
 
 system("pause");
 return 0;
}

解决死锁的代价比较大,所以一般解决办法是 通过良好的设计 避免 死锁

标准库提供了一套 避免死锁的方案

std::lock  该锁 把需要访问的资源 要么都加锁 要么都不加锁 来避免死锁

template<class _Lock0,
	class _Lock1,
	class... _LockN> inline
	void lock(_Lock0& _Lk0, _Lock1& _Lk1, _LockN&... _LkN)
	{	// lock N mutexes
	int _Res = 0;
	while (_Res != -1)
		_Res = _Try_lock(_Lk0, _Lk1, _LkN...);
	}

lock_guard可以通过调用重载构造函数 来避免加锁 ,来RAII资源

	std::lock_guard<std::mutex>  l(m,std::adopt_lock);

避免死锁的一些方法

避免嵌套锁
一个线程已经获得了一个锁的同时 别去获取第二个 如果需要 那就用std::lock 来获取

避免在持有锁的情况下调用用户代码,因为你不知道永不要做什么

使用固定顺序获取锁,如获取了A才能获取B,这样就不会导致循环等待的情况, 当然std::lock的原理就是这个





Scroll Up