Python 简明教程

Python - Thread Deadlock

死锁可以描述为并发故障模式。它是程序中一个或多个线程等待永远不会发生的条件的情况。结果,该线程无法继续进行,程序将卡住或冻结,且必须手动终止。

在并发程序中,可能会以多种方式出现死锁情况。死锁永远不会有意地发展,相反,它们实际上是代码中的副作用或错误。

下面列出了线程死锁的常见原因 −

  1. 尝试两次获取同一互斥锁的线程。

  2. 相互等待的线程(例如,A 等待 B,B 等待 A)。

  3. 当线程未能释放诸如锁、信号量、条件、事件等资源时。

  4. 以不同顺序获取互斥锁的线程(例如,未执行锁定顺序)。

How to Avoid Deadlocks in Python Threads

当多线程应用程序中的多个线程尝试访问同一资源时,例如对同一文件执行读/写操作,它会导致数据不一致。因此,使用 locking mechanisms 同步对资源的并发访问非常重要。

Python threading 模块提供了一种简单的锁定机制来 synchronize threads 。你可以通过调用 Lock() 类来创建一个新的锁定对象,它将锁定初始化为解锁状态。

Locking Mechanism with the Lock Object

一个 Lock 类的对象有两个可能状态——锁定或解锁,创建时最初为解锁状态。锁定不属于任何特定线程。

锁定类定义了 acquire() 和 release() 方法。

The acquire() Method

锁定类的 acquire() 方法将锁定的状态从解锁改为锁定。它立即返回,除非可选的锁定参数被设置为 True,在这种情况下,它将一直等到获得锁定。

以下是这个方法的 Syntax

Lock.acquire(blocking, timeout)

其中,

  1. blocking − 如果设置为 False,意味着不锁定。如果带有锁定设置为 True 的调用将锁定,立即返回 False;否则,将锁定设置为锁定并返回 True。

  2. timeout − 指定用于获取锁定的超时时间间隔。

这个方法的返回值如果成功获取锁定,则为 True;如果失败,则为 False。

The release() Method

当状态被锁定时,另一个线程中的这个方法会将其改为解锁。它可以被任何线程调用,不仅限于获取锁定的线程

以下是 release() 方法的 Syntax

Lock.release()

release() 方法只能在锁定状态下调用。如果尝试释放一个未锁定的锁定,会引发 RuntimeError。

当锁定被锁定时,将它重置为解锁,并返回。如果有任何其他线程因为等待锁定被解锁而锁定,只允许其中一个继续。这个方法没有返回值。

Example

在以下程序中,有两个线程尝试调用 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")

它将生成以下 output

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

Semaphore Object for Synchronization

除了锁定之外,Python threading 模块还支持 semaphores ,它提供了另一种同步技术。它是著名计算机科学家 Edsger W. Dijkstra 发明的最古老的同步技术之一。

信号量的基本概念是使用一个由每次 acquire() 调用递减,且每次 release() 调用递增的内部计数器。计数器永远不会低于零;当 acquire() 发现它是零时,它将锁定,一直等到其他线程调用 release() 为止。

线程模块中的信号量类定义了 acquire() 和 release() 方法。

The acquire() Method

如果输入时内部计数器大于零,将其减一并立即返回 True。

如果输入时内部计数器为零,则锁定,直到因调用 release() 而被唤醒。一旦唤醒(并且计数器大于 0),将计数器减 1 并返回 True。每次调用 release(),都将唤醒一个线程。唤醒线程的顺序是任意的。

如果阻塞参数被设置为 False,不要阻塞。如果未带参数的调用将阻塞,立即返回 False;否则,执行与未带参数调用时相同的操作,并返回 True。

The release() Method

释放一个信号量,将内部计数器加 1。当在进入时它为零,并且其他线程正在等待它再次变为大于零时,唤醒其中 n 个线程。

Example

此示例演示了如何在 Python 中使用 ^ 对象来控制多个线程对共享资源的访问,以避免 Python 多线程程序中的死锁。

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()

它将生成以下 output

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