在平时的应用设计中,由于出现多线程(进程)的并发应用,以及分布式应用,比较容易出现死锁现象。
下面来看一个简单的实例:
有两个独立的链表,假定使用两个独立的锁来保护他们,以便能够独立的访问。再假定某个操作要求遍历一个列表所检索到的元素必须从两个列表中断开连接,
而且必须以一次
原子操作来完成。
线程1 | 线程2
lock(&lock_a); | lock(&lock_b);
find element to unlink on list a | find element to unlink on list b
lock(&lock_b); | lock(&lock_a);
unlink element from both lists | unlink element from both lists
unlock(&lock_b); | unlock(&lock_a);
unlock(&lock_a); | unlock(&lock_b);
可能出现死锁的情况。
这要求在消除一个元素是必须同时拥有两个列表的锁。对第一个线程要先或得锁lock_a, 然后要或得锁lock_b.
第2个线程正好相反,它先或得锁lock_b,然后再获取锁lock_a.
如果某一时刻,第一个线程执行到了 find element to unlink on list a, 而第二个线程此时执行到了find element to unlink on list b, 则这两个线程将发生死锁。
这种死锁称为AB-BA死锁。 (注,死锁的发生是跟两个线程的执行时序相关的,例如,第一个线程执行完了所有的这部分代码,线程2才开始执行此段代码,则不会发生死锁。)
如果某个锁不是递归锁,例如lock_a, 而线程1在应用中对它进行多个调用,而没有调用解锁操作,也会发生死锁。
代码示例如下:
lock(&lock_a);
other logic code
lock(&lock_a);
other logic code
unlock(&lock_a);
unlock(&lock_a);
防止死锁的办法: 为了防止发生这类死锁,所有线程必须以相同的次序获得嵌套锁,即以相同的次序获得且同时占有锁。
可以把上面的代码改成如下代码来避免死锁:
lock(&lock_a);
lock(&lock_b);
find element to unlink on list a or b
unlink element from both lists
unlock(&lock_b);
unlock(&lock_a);
当涉及到3个或者更多锁的时候也是如此:只要各线程在获得和释放锁的时候保持相同的次序,那么就不会出现死锁。