死锁是多线程编程中经常讨论的问题,所谓死锁,就是线程一直无限期地等待某个资源。
最简单的死锁现象就是一个线程等待一个自己已经拿到的锁。由于该锁已经被自己拿到了,所以第二次申请该锁时会被放到等待队列中,但这个等待的时间是永远。下面的代码演示了这种情况。
import sys, time
if sys.version_info.major == 2:
import thread
else:
import _thread as thread
lock = thread.allocate_lock() # 创建一个锁
def thread_entry(): # 线程入口函数
global lock
print("Before lock Acquire - 1")
lock.acquire()
print("After lock Acquire - 1")
print("Before lock Acquire - 2")
lock.acquire() # 死锁在这里,后面的代码不会继续执行
print("After lock Acquire - 1")
lock.release()
lock.release()
def start_threads(): # 启动子线程
t1 = thread.start_new_thread(thread_entry, tuple())
time.sleep(5)
print("Main Thread Quit") # 主线程退出
if __name__=='__main__':
start_threads()
运行结果如下:
$ python deadlockDemo1.py
Before lock Acquire – 1 # 第9行的输出
After lock Acquire – 1 # 第11行的输出
Before lock Acquire – 2 # 第12行的输出
Main Thread Quit # 第20行的输出,子进程卡在第13行
前面介绍的是自己将自己给锁死了,这种情况相对来说较少,更多的情况是这样的:线程 A 得到某个资源 R1,同时去申请资源 R2,线程 B 得到了资源 R2,同时去申请资源 R1。这时就出现了死锁,线程 A 因为得不到资源 R2 而一直处于等待状态,线程 B 也因为得不到资源 R1 而一直处于等待状态。下面的代码演示了这种情况。
import sys, time # 引入time库
if sys.version_info.major == 2: # Python 2
import thread
else: # Python 3
import _thread as thread
lock1 = thread.allocate_lock() # 资源R1
lock2 = thread.allocate_lock() # 资源R2
def thread_entry_A(): # 线程A的入口函数
global lock1, lock2
print("Thread A: Before lock1 Acquire")
lock1.acquire() # 得到资源R1
print("Thread A: After lock1 Acquire")
time.sleep(3)
print("Thread A: Before lock2 Acquire")
lock2.acquire() # 申请资源R2,死锁在这里
print("Thread A: After lock2 Acquire")
lock1.release() # 释放资源R1
lock2.release() # 释放资源R2
def thread_entry_B(): # 线程B的入口函数
global lock1, lock2
print("Thread B: Before lock2 Acquire")
lock2.acquire() # 得到资源R2
print("Thread B: After lock2 Acquire")
time.sleep(3)
print("Thread B: Before lock1 Acquire")
lock1.acquire() # 申请资源R1,死锁在这里
print("Thread B: After lock1 Acquire")
lock1.release() # 释放资源R1
lock2.release() # 释放资源R2
def start_threads():
t1 = thread.start_new_thread(thread_entry_A, tuple())
t1 = thread.start_new_thread(thread_entry_B, tuple())
time.sleep(5)
print("Main Thread Quit") # 主线程退出,进程也退出
if __name__=='__main__': # 如果是运行脚本而不是引入该模块
start_threads()
运行结果如下:
$ python deadlockDemo2.py # 运行脚本
Thread A: Before lock1 Acquire # 第10行的输出
Thread A: After lock1 Acquire # 第12行的输出
Thread B: Before lock2 Acquire # 第21行的输出
Thread B: After lock2 Acquire # 第23行的输出
Thread A: Before lock2 Acquire # 第14行的输出,进入死锁状态
Thread B: Before lock1 Acquire # 第25行的输出,进入死锁状态
Main Thread Quit # 主线程结束
死锁是发生在线程之间的,一般是因为某个线程 A 希望得到另外一个线程 B 的某个资源,所以可以用图 1 来表示这个关系。
但是这不会导致死锁,因为线程 B 会释放其所拥有的资源,那时线程 A 就可以继续运行了,死锁的一个条件就是所有参与死锁的线程都无法继续运行下去。在前面的例子中,线程 A 是无法继续运行下去的,但是线程 B 是可以继续运行下去的,所以这时是没有死锁发生的。如果线程 B 也在等 A 手中的某个资源呢?那么 B 也不能继续运行下去了,这就是前面例子代码演示的情况,可以用图 2 来表示。
可以发现这时图上形成了一个环,即 A 依赖于 B,B 又依赖于 A。一旦形成了环,那么就出现了死锁现象。而对于本节的第一个例子,即自己锁死自己的那个例子,可以用图 3 来表示。
线程死锁一般都不是我们期望的结果,所以应该尽量避免出现。而且现实中的死锁并不像例子中演示的那样,每次都会确定发生,而是表现为一种随机现象,即有时工作的很好,有时就进入了死锁状态,所以最根本的方法是谨慎设计以防止这种现象出现。
在编码时也有一些小技巧可以使用,如不要无限期等待某个资源,而是设定一个等待时限。例如设置某个线程最多等待 10 秒钟,那么该锁在 10 秒钟后便可以自动解开。