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