死锁是一种程序中的情况,其中一个或多个线程等待一个永远不会发生的条件。结果,这些线程无法进展,程序停滞或冻结,必须手动终止。
死锁可能以多种方式出现在您的并发程序中。死锁从来不是有意开发的,而是代码中的副作用或错误。
导致线程死锁的常见原因如下:
-
-
线程相互等待(例如,A 等待 B,B 等待 A)。
-
-
线程以不同顺序获取互斥锁(例如,未能执行锁排序)。
如何避免 Python 线程中的死锁
当多线程应用程序中的多个线程尝试访问相同的资源,比如对同一个文件进行读写操作时,可能导致数据不一致。因此,重要的是使用锁定机制来同步对资源的并发访问。
Python 的 threading 模块提供了一种易于实现的锁定机制来同步线程。您可以通过调用 Lock() 类创建一个新的锁对象,初始化时锁处于未锁定状态。
使用 Lock 对象的锁定机制
Lock 类的对象有两种可能的状态——锁定或未锁定,初始创建时为未锁定状态。一个锁不属于任何特定的线程。
Lock 类定义了 acquire() 和 release() 方法。
acquire() 方法
Lock 类的 acquire() 方法将锁的状态从未锁定改为锁定。除非可选的 blocking 参数设置为 True,否则它会立即返回。在这种情况下,它会等待直到锁被获取。
以下是该方法的语法:
Lock.acquire(blocking, timeout)
其中,
-
blocking - 如果设置为 False,意味着不要阻塞。如果带有 blocking 设置为 True 的调用将会阻塞,则立即返回 False;否则,将锁设置为锁定并返回 True。
-
该方法如果成功获取锁则返回值为 True;如果不成功则为 False。
release() 方法
当状态为锁定时,此方法在另一个线程中将其改为未锁定。这可以从任何线程调用,而不仅仅是获取锁的线程。
以下是 release() 方法的语法:
Lock.release()
release() 方法应该只在锁定状态下调用。如果尝试释放未锁定的锁,将引发 RuntimeError。
当锁被锁定时,将其重置为未锁定并返回。如果有其他线程被阻塞等待锁变得未锁定,则恰好允许其中一个线程继续。此方法没有返回值。
示例
在下面的程序中,两个线程试图调用 synchronized() 方法。其中一个获取锁并获得访问权限,而另一个则等待。当第一个线程的 run() 方法完成后,锁被释放,synchronized 方法可供第二个线程使用。
当两个线程都加入后,程序结束。
from threading import Thread, Lock
import time
lock = Lock()
threads = []
class myThread(Thread):
def __init__(self, name):
Thread.__init__(self)
self.name = name
def run(self):
lock.acquire()
synchronized(self.name)
lock.release()
def synchronized(threadName):
print("{} 已经获取锁并正在运行同步方法".format(threadName))
counter = 5
while counter:
print('**', end='')
time.sleep(2)
counter -= 1
print('\n锁已被释放', threadName)
t1 = myThread('Thread1')
t2 = myThread('Thread2')
t1.start()
threads.append(t1)
t2.start()
threads.append(t2)
for t in threads:
t.join()
print("主线程结束")
它将产生以下输出:
Thread1 已经获取锁并正在运行同步方法
**********
锁已被释放 Thread1
Thread2 已经获取锁并正在运行同步方法
**********
锁已被释放 Thread2
主线程结束
使用 Semaphore 对象进行同步
除了锁之外,Python 的 threading 模块还支持信号量,这是由著名计算机科学家 Edsger W. Dijkstra 发明的最古老的同步技术之一。
信号量的基本概念是使用一个内部计数器,每次调用 acquire() 时递减,每次调用 release() 时递增。计数器永远不能低于零;当 acquire() 发现它是零时,它将阻塞,直到其他线程调用 release()。
threading 模块中的 Semaphore 类定义了 acquire() 和 release() 方法。
acquire() 方法
如果进入时内部计数器大于零,则递减计数器并立即返回 True。
如果进入时内部计数器为零,则阻塞直到被 release() 调用唤醒。一旦被唤醒(且计数器大于零),递减计数器并返回 True。每次调用 release() 恰好唤醒一个线程。线程唤醒的顺序是任意的。
如果 blocking 参数设置为 False,则不要阻塞。如果无参数调用将会阻塞,则立即返回 False;否则,做同样的事情并返回 True。
release() 方法
释放信号量,将内部计数器递增 1。当它进入时为零且其他线程正等待它再次大于零时,唤醒 n 个这样的线程。
示例
此示例演示了如何在 Python 中使用 Semaphore 对象来控制多个线程之间对共享资源的访问,从而避免 Python 多线程程序中的死锁。
from threading import *
import time
lock = Semaphore(4)
def synchronized(name):
lock.acquire()
for n in range(3):
print('Hello! ', end='')
time.sleep(1)
print(name)
lock.release()
thread_1 = Thread(target=synchronized, args=('Thread 1',))
thread_2 = Thread(target=synchronized, args=('Thread 2',))
thread_3 = Thread(target=synchronized, args=('Thread 3',))
thread_1.start()
thread_2.start()
thread_3.start()
它将产生以下输出:
Hello! Hello! Hello! Thread 1
Hello! Thread 2
Thread 3
Hello! Hello! Thread 1
Hello! Thread 3
Thread 2
Hello! Hello! Thread 1
Thread 3
Thread 2