模拟死锁 3:以错误的顺序获取锁
导致死锁的一个常见原因是,两个线程同时以不同的顺序获得锁。例如,我们可能有一个受锁保护的关键部分,在这个关键部分中,我们可能有代码或函数调用受第二个锁保护。
可能会遇到这样的情况:一个线程获得了锁 1 ,然后试图获得锁 2,然后有第二个线程调用获得锁 2 的功能,然后试图获得锁 1。如果这种情况同时发生,线程 1 持有锁 1,线程 2 持有锁 2,那么就会有一个死锁。
- 线程 1: 持有锁 1, 等待锁 2
- 线程 2 : 持有锁 2, 等待锁 1
# SuperFastPython.com # example of a deadlock caused by acquiring locks in a different order from time import sleep from threading import Thread from threading import Lock # task to be executed in a new thread def task(number, lock1, lock2): # acquire the first lock print(f'Thread {number} acquiring lock 1...') with lock1: # wait a moment sleep(1) # acquire the next lock print(f'Thread {number} acquiring lock 2...') with lock2: # never gets here.. pass # create the mutex locks lock1 = Lock() lock2 = Lock() # create and configure the new threads thread1 = Thread(target=task, args=(1, lock1, lock2)) thread2 = Thread(target=task, args=(2, lock2, lock1)) # start the new threads thread1.start() thread2.start() # wait for threads to exit... thread1.join() thread2.join()
运行这个例子首先创建了两个锁。然后两个线程都被创建,主线程等待线程的终止。
第一个线程接收 lock1 和 lock2 作为参数。它获得了锁 1 并 sleep。
第二个线程接收 lock2 和 lock1 作为参数。它获得了锁 2 并 sleep。
第一个线程醒来并试图获取锁 2,但它必须等待,因为它已经被第二个线程获取。第二个线程醒来并试图获取锁 1,但它必须等待,因为它已经被第一个线程获取。
结果是一个死锁:
Thread 1 acquiring lock 1... Thread 2 acquiring lock 1... Thread 1 acquiring lock 2... Thread 2 acquiring lock 2...
解决办法是确保锁在整个程序中总是以相同的顺序获得。这就是所谓的锁排序。
模拟死锁 4:锁未释放
导致死锁的另一个常见原因是线程未能释放一个资源。这通常是由线程在关键部分引发错误或异常造成的,这种方式会阻止线程释放资源,包括:
- 未能释放一个锁
- 未能释放一个信号器
- 未能到达一个 barrier
- 未能在一个条件上通知线程
- 未能设置一个事件
# example of a deadlock caused by a thread failing to release a lock from time import sleep from threading import Thread from threading import Lock # task to be executed in a new thread def task(lock): # acquire the lock print('Thread acquiring lock...') lock.acquire() # fail raise Exception('Something bad happened') # release the lock (never gets here) print('Thread releasing lock...') lock.release() # create the mutex lock lock = Lock() # create and configure the new thread thread = Thread(target=task, args=(lock,)) # start the new thread thread.start() # wait a while sleep(1) # acquire the lock print('Main acquiring lock...') lock.acquire() # do something... # release lock (never gets here) lock.release()
运行该例子时,首先创建锁,然后创建并启动新的线程。然后主线程阻塞。新线程运行。它首先获得了锁,然后引发了一个异常而失败。该线程解开了锁,但却没有解开锁的代码。新的线程终止了。最后,主线程被唤醒,然后试图获取锁。由于锁没有被释放,主线程永远阻塞,导致了死锁。
Thread acquiring lock... Exception in thread Thread-1: Traceback (most recent call last): ... Exception: Something bad happened Main acquiring lock...
总结
本文首先介绍了并发编程中的经典问题——哲学家就餐问题,然后引出了死锁的概念及条件。
然后给出了可能出现死锁的情况,并通过 Python 代码模拟哲学家就餐问题和模拟死锁的四种情况。