Python 线程死锁
死锁可以描述为一种并发故障模式。它是程序中的一种情况,其中一个或多个线程等待从未发生的条件。结果,线程无法继续执行,程序被卡住或冻结,必须手动终止。
并发程序中可能以许多方式出现死锁情况。死锁从未被有意开发,而是实际上是代码中的副作用或错误。
线程死锁的常见原因如下:
- 尝试两次获取相同的互斥锁的线程。
-
互相等待的线程(例如A等待B,B等待A)。
-
线程未释放资源(例如锁,信号量,条件,事件等)。
-
以不同的顺序获取互斥锁的线程(例如无法执行锁排序)。
如果多个多线程应用程序中的线程尝试访问同一资源,例如对同一文件执行读写操作,可能会导致数据不一致。因此,重要的是使用同步方式处理并发,以便当一个线程使用资源时,它对其他线程进行了锁定。
Python提供的线程模块包括一种简单易实现的锁定机制,可以用来同步线程。通过调用Lock()方法创建一个新的锁,该方法返回新的锁。
锁对象
Lock类的对象有两种可能的状态 – 锁定或解锁,初始状态为解锁。锁不属于任何特定线程。
Lock类定义了acquire()和release()方法。
acquire()方法
当状态为解锁时,此方法将状态更改为锁定并立即返回。该方法接受一个可选的阻塞参数。
语法
Lock.acquire(blocking, timeout)
参数
- blocking − 如果设置为False,则表示不阻塞。如果将blocking设置为True的调用将会阻塞,立即返回False;否则,将锁定设置为已锁定,并返回True。
此方法的返回值为True表示成功获取锁;False表示未获取锁。
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 ("{} has acquired lock and is running synchronized method".format(threadName))
counter=5
while counter:
print ('**', end='')
time.sleep(2)
counter=counter-1
print('\nlock released for', threadName)
t1=myThread('Thread1')
t2=myThread('Thread2')
t1.start()
threads.append(t1)
t2.start()
threads.append(t2)
for t in threads:
t.join()
print ("end of main thread")
它将生成以下 输出 −
Thread1 has acquired lock and is running synchronized method
**********
lock released for Thread1
Thread2 has acquired lock and is running synchronized method
**********
lock released for Thread2
end of main thread
信号量对象
Python使用另一种称为信号量的机制来支持线程同步 semaphore 。这是一种由著名计算机科学家Edsger W. Dijkstra发明的最古老的同步技术之一。
信号量的基本概念是使用一个内部计数器,每个acquire()调用将其递减,每个release()调用将其递增。计数器永远不能低于零;当acquire()发现计数器为零时,它会阻塞,直到某个其他线程调用release()。
线程模块中的Semaphore类定义了acquire()和release()方法。
acquire()方法
如果内部计数器在进入时大于零,则将其减一并立即返回True。
如果内部计数器在进入时为零,则阻塞直到通过调用release()唤醒。一旦唤醒(且计数器大于0),将计数器减一并返回True。每次调用release()都会唤醒一个线程。唤醒线程的顺序是任意的。
如果将blocking参数设置为False,则不阻塞。如果不带参数的调用会阻塞,则立即返回False;否则,执行与不带参数调用相同的操作,并返回True。
release()方法
释放信号量,将内部计数器增加1。如果在进入时计数器为零且其他线程正在等待计数器再次大于零,则唤醒其中的n个线程。
示例
from threading import *
import time
# creating thread instance where count = 3
lock = Semaphore(4)
# creating instance
def synchronized(name):
# calling acquire method
lock.acquire()
for n in range(3):
print('Hello! ', end = '')
time.sleep(1)
print( name)
# calling release method
lock.release()
# creating multiple thread
thread_1 = Thread(target = synchronized , args = ('Thread 1',))
thread_2 = Thread(target = synchronized , args = ('Thread 2',))
thread_3 = Thread(target = synchronized , args = ('Thread 3',))
# calling the threads
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