Python 中的執行緒鎖
本教程將討論在 Python 中使用執行緒鎖的不同方法。
Python 中的競爭條件
競爭條件是當多個執行緒嘗試修改同一個共享變數時出現的問題。所有執行緒同時從共享變數中讀取相同的值。然後,所有執行緒嘗試修改共享變數的值。但是,該變數最終只會儲存最後一個執行緒的值,因為它會覆蓋前一個執行緒寫入的值。從這個意義上說,所有執行緒之間存在競爭,以檢視最終哪個執行緒修改了變數的值。以下程式碼中的示例演示了這種現象。
from threading import Thread
counter = 0
def increase(by):
global counter
local_counter = counter
local_counter += by
counter = local_counter
print(f"counter={counter}")
t1 = Thread(target=increase, args=(10,))
t2 = Thread(target=increase, args=(20,))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"The final counter is {counter}")
輸出:
counter=10
counter=20
The final counter is 20
我們有一個全域性共享變數 counter = 0
和兩個執行緒 t1
和 t2
。執行緒 t1
嘗試將 counter
的值增加 10,執行緒 t2
嘗試將 counter
的值增加 20。在上面的程式碼中,我們同時執行兩個執行緒並嘗試修改值計數器
。根據上述邏輯,counter
的最終值應該是 30。但是,由於競爭條件,counter
要麼是 10,要麼是 20。
Python 中的執行緒鎖
執行緒鎖用於防止競爭條件。執行緒鎖在被一個執行緒使用時鎖定對共享變數的訪問,以便任何其他執行緒無法訪問它,然後線上程不使用共享變數時移除鎖,以便其他執行緒可以使用該變數進行處理。threading 模組中的 Lock
class 用於在 Python 中建立執行緒鎖。acquire()
方法用於鎖定對共享變數的訪問,而 release()
方法用於解鎖鎖定。如果在未鎖定的鎖上使用 release()
方法會引發 RuntimeError
異常。
from threading import Thread, Lock
counter = 0
def increase(by, lock):
global counter
lock.acquire()
local_counter = counter
local_counter += by
counter = local_counter
print(f"counter={counter}")
lock.release()
lock = Lock()
t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"The final counter is {counter}")
輸出:
counter=10
counter=30
The final counter is 30
我們建立了一個全域性共享變數 counter=0
和兩個執行緒 t1
和 t2
。兩個執行緒都針對相同的 increase()
函式。increase(by, lock)
函式有兩個引數。第一個引數是它將增加 counter
的數量,第二個引數是 Lock
類的例項。除了前面的宣告,我們還在 Python 的 threading
模組中建立了一個 Lock
類的例項 lock
。increase(by, lock)
函式中的這個 lock
引數使用 lock.acquire()
函式鎖定對 counter
變數的訪問,同時它被任何執行緒修改,並使用 lock 解鎖鎖定。release()
函式,當一個執行緒修改了 counter
變數。執行緒 t1
將 counter
的值增加 10,執行緒 t2
將 counter
的值增加 20。
由於執行緒鎖定,不會發生競爭條件,最終 counter
的值為 30。
在 Python 中使用 with lock:
的執行緒鎖
前一種方法的問題在於,當一個執行緒完成處理時,我們必須小心地解鎖每個鎖定的變數。如果沒有正確完成,我們的共享變數將只會被第一個執行緒訪問,而其他執行緒將無法訪問共享變數。這個問題可以通過使用上下文管理來避免。我們可以使用 with lock:
並將我們所有的關鍵程式碼放在這個塊中。這是防止競爭條件的更簡單的方法。以下程式碼片段顯示了使用 with lock:
來防止 Python 中的競爭條件。
from threading import Thread, Lock
counter = 0
def increase(by, lock):
global counter
with lock:
local_counter = counter
local_counter += by
counter = local_counter
print(f"counter={counter}")
lock = Lock()
t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"The final counter is {counter}")
輸出:
counter=10
counter=30
The final counter is 30
我們將增加 counter
的程式碼放在 with lock:
塊中。執行緒 t1
將 counter
的值增加 10,執行緒 t2
將 counter
的值增加 20。沒有發生競爭條件,counter
的最終值為 30。此外,我們不需要擔心解鎖執行緒鎖。
兩種方法都完美地完成了它們的工作,即兩種方法都可以防止競爭條件的發生,但是第二種方法遠遠優於第一種,因為它可以避免我們為處理執行緒鎖的鎖定和解鎖而頭疼。與第一種方法相比,它編寫起來也更簡潔,更易於閱讀。
Maisam is a highly skilled and motivated Data Scientist. He has over 4 years of experience with Python programming language. He loves solving complex problems and sharing his results on the internet.
LinkedIn