Python - 线程死锁



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

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

线程死锁的常见原因列在下面:

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

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

  • 当线程未能释放资源(例如锁、信号量、条件、事件等)。

  • 以不同顺序获取互斥锁的线程(例如,未能执行锁排序)。

如何在 Python 线程中避免死锁

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

Python 的threading模块提供了一种易于实现的锁定机制来同步线程。您可以通过调用Lock()类创建一个新的锁对象,该类将锁初始化为未锁定状态。

使用 Lock 对象的锁定机制

Lock类的对象有两种可能的状态:锁定或未锁定,最初在创建时处于未锁定状态。锁不属于任何特定线程。

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

acquire() 方法

acquire()方法更改锁的状态从未锁定到锁定。除非可选的 blocking 参数设置为 True,否则它会立即返回,在这种情况下,它会等待直到获取锁。

以下是此方法的语法

Lock.acquire(blocking, timeout)

其中,

  • blocking - 如果设置为 False,则表示不阻塞。如果带有 blocking 设置为 True 的调用将被阻塞,则立即返回 False;否则,将锁设置为锁定并返回 True。

  • timeout - 指定获取锁的超时时间。

此方法的返回值为 True,如果锁成功获取;否则为 False。

release() 方法

当状态被锁定时,另一个线程中的此方法将其更改为解锁状态。这可以从任何线程调用,而不仅仅是从获取锁的线程调用。

以下是release()方法的语法

Lock.release()

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

当锁被锁定时,将其重置为解锁状态,并返回。如果任何其他线程被阻塞等待锁解锁,则允许其中恰好一个线程继续执行。此方法没有返回值。

示例

在下面的程序中,两个线程尝试调用synchronized()方法。其中一个线程获取锁并获得访问权限,而另一个线程则等待。当第一个线程的run()方法完成时,锁被释放,第二个线程可以使用synchronized方法。

当两个线程都join后,程序结束。

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的threading模块还支持信号量,它提供了一种其他的同步技术。它是著名的计算机科学家Edsger W. Dijkstra发明的一种最古老的同步技术之一。

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

threading模块中的Semaphore类定义了acquire()和release()方法。

acquire() 方法

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

如果在进入时内部计数器为零,则阻塞直到被对release()的调用唤醒。一旦被唤醒(并且计数器大于0),则将其减一并返回True。每个对release()的调用将唤醒恰好一个线程。线程唤醒的顺序是任意的。

如果blocking参数设置为False,则不阻塞。如果一个没有参数的调用会阻塞,则立即返回False;否则,执行与没有参数调用时相同的事情,并返回True。

release() 方法

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

示例

此示例演示了如何在Python中使用Semaphore对象来控制多个线程对共享资源的访问,以避免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()

它将产生以下输出

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
广告