最近项目中用到一些多线程的知识,涉及到一个线程需要锁住多个资源的情况,这就会涉及到多线程的死锁问题。特此总结一下
死锁产生的方式有好几种,并不是只有一个线程涉及多个锁才会出现死锁的情况,单个锁也有可能出现死锁。
1、第一种常见的情况是加锁之后没有解锁。有可能是lock之后真的忘了unlock,这种比较少见也容易发现。但是有时候程序并不是跟我们预想的一样一帆风顺的走完流程,可能是在lock和unlock之间的代码出现了异常退出,这样就造成了加锁之后没有解锁,后续程序对该锁的请求无法实现,导致死锁等待。
解决方法:在c++语言中,就是利用所谓的Autolock局部对象,在该对象的构造函数中lock,在析构函数中unlock,因为是在栈中创建的对象,系统会自动执行析构函数,即使程序异常退出也会执行析构函数从而释放资源。
2、第二种是同一个线程中对同一个资源多次调用lock函数。有的互斥锁对象没有线程所有权属性,比如windows下的信号量Semaphores ,即一个线程获得某个信号量后,在他释放该信号量之前,他不能再次进入信号量保护的区域。如果信号量的计数只有1,同一个线程调用WaitForSingleObject两次,程序就会阻塞在第二次调用处造成死锁。
3、第三种情况就是我们通常所说的情况。有两个线程,线程1和线程2,有两个锁A和B,线程1持有A然后等待B,与此同时线程1持有B然后等待A。两个线程都不释放拥有的锁,也都获取不到等待的锁。
避免死锁一般针对的是第三种情况。
1、尽量不在同一个线程中同时锁住两个临界资源,不过如果业务要求必须这样,那就没办法。
2、有一种可行的办法是,多个线程对多个锁的加锁顺序一样,这样就不会发生死锁,比如线程1先对A资源加锁,再对B资源加锁,线程2也使用相同的顺序,就不会产生死锁。
3、还有一种可行的方案是一次性获取所有需要获取的锁,如果不能一次性获取则等待。我想了一下linux下可以用pthread_mutex_trylock函数来实现,伪代码如下(针对两个锁而言):
while (true)
{
pthread_mutex_lock(&mutexA);
if (0 == pthread_mutex_trylock(&mutexB))
{
//成功获取所有锁
break;
}
else
{
//释放持有的锁,继续等待
pthread_mutex_unlock(&mutexA);
//延时一段时间再继续请求
usleep(10*1000);
}
}
//业务代码
...
pthread_mutex_unlock(&mutexA);
pthread_mutex_unlock(&mutexB);
4、还有一种方法是使用等待超时机制,如果等待一个锁太久没得到,就释放自己拥有的所有锁,避免死锁。这个方法感觉指标不治本,是处理死锁发生后的情况而不是有效的预防死锁。
5、听说还有一种是死锁检测机制。mysql数据库就具备死锁检测机制,死锁检测的算法目前没有研究过,感兴趣的同学可以自行研究。