模拟死锁 1:线程等待本身
导致死锁的一个常见原因是线程在自己身上等待。
我们并不打算让这种死锁发生,例如,我们不会故意写代码,导致线程自己等待。相反,由于一系列的函数调用和变量的传递,这种情况会意外地发生。
一个线程可能会因为很多原因而在自己身上等待,比如:
- 等待获得它已经获得的互斥锁
- 等待自己被通知一个条件
- 等待一个事件被自己设置
- 等待一个信号被自己释放
开发一个 task()
函数,直接尝试两次获取同一个 mutex 锁。也就是说,该任务将获取锁,然后再次尝试获取锁。
# task to be executed in a new thread def task(lock): print('Thread acquiring lock...') with lock: print('Thread acquiring lock again...') with lock: # will never get here pass
这将导致死锁,因为线程已经持有该锁,并将永远等待自己释放该锁,以便它能再次获得该锁, task()
试图两次获取同一个锁并触发死锁。
在主线程中,可以创建锁:
# create the mutex lock lock = Lock()
然后我们将创建并配置一个新的线程,在一个新的线程中执行我们的 task()
函数,然后启动这个线程并等待它终止,而它永远不会终止。
# create and configure the new thread thread = Thread(target=task, args=(lock,)) # start the new thread thread.start() # wait for threads to exit... thread.join()
完整代码如下:
from threading import Thread from threading import Lock # task to be executed in a new thread def task(lock): print('Thread acquiring lock...') with lock: print('Thread acquiring lock again...') with lock: # will never get here pass # 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 for threads to exit... thread.join()
运行结果如下:
首先创建锁,然后新的线程被混淆并启动,主线程阻塞,直到新线程终止,但它从未这样做。
新线程运行并首先获得了锁。然后它试图再次获得相同的互斥锁并阻塞。
它将永远阻塞,等待锁被释放。该锁不能被释放,因为该线程已经持有该锁。因此,该线程已经陷入死锁。
该程序必须被强制终止,例如,通过 Control-C 杀死终端。
模拟死锁 2:线程互相等待
一个常见的例子就是两个或多个线程互相等待。例如:线程 A 等待线程 B,线程 B 等待线程 A。
如果有三个线程,可能会出现线程循环等待,例如:
- 线程 A:等待线程 B
- 线程 B:等待线程 C
- 线程 C:等待线程 A
如果你设置了线程来等待其他线程的结果,这种死锁是很常见的,比如在一个流水线或工作流中,子任务的一些依赖关系是不符合顺序的。
from threading import current_thread from threading import Thread # task to be executed in a new thread def task(other): # message print(f'[{current_thread().name}] waiting on [{other.name}]...\n') other.join() # get the current thread main_thread = current_thread() # create the second thread new_thread = Thread(target=task, args=(main_thread,)) # start the new thread new_thread.start() # run the first thread task(new_thread)
首先得到主线程的实例 main_thread
,然后创建一个新的线程 new_thread
,并调用传递给主线程的 task()
函数。新线程返回一条信息并等待主线程停止,主线程用新线程的实例调用 task()
函数,并等待新线程的终止。每个线程都在等待另一个线程终止,然后自己才能终止,这导致了一个死锁。
运行结果:
[Thread-1] waiting on [MainThread]... [MainThread] waiting on [Thread-1]...