Python 線程同步
- Python 線程同步
Python 線程同步
線程同步是一種確保兩個或多個線程不同時執行同一塊共享代碼的機制。共享塊中的代碼通常是訪問共享數據或資源,這種共享塊被稱作臨界區。這個概念可以用下面的圖清晰地表示出來:
多個線程在同一時間記問臨界區,就會有可能同時嘗試訪問或改變數據,這有可能會導致不可預知的后果。這種情況被稱作競態條件。
為了講解什么是競態條件,我們將實現一個包含兩個線程的簡單的程序,每個線程都會給一個共享變量增加值一百萬次。我們選擇一個較大的數,是為了確保發現競態條件。在速度比較低的 CPU 上,用一個小一點的數,也有可能發現競態條件。在這個程序中,我們使用同一個函數(inc
)創建兩個線程。訪問共享變量并且每次給它加 1 的代碼在臨界區里,兩個線程訪問這個變量時,沒有做任何保護。下面是完整的例子:
from threading import Thread, current_thread
from time import sleepdef inc():global xfor _ in range(1000000):x += 1# 全局變量
x = 0# 創建線程
t1 = Thread(target=inc, name="線程 1")
t2 = Thread(target=inc, name="線程 2")# 啟動線程
t1.start()
t2.start()# 等待線程結束
t1.join()
t2.join()print("最后 x 的值是:", x)
在上面的這個程序中,我們期望 x
最后的值是 2000000
,但是在程序的輸出結果里,我們可能看不到 2000000
。每次執行這個程序,都有可能看到不一樣的結果,這個結果會比 2000000
小一些。讓我們看一下代碼,線程 1
和 線程 2
同時執行臨界區代碼 x += 1
。兩個線程都會請求 x
的當前值。如果當前的值是 1000
,兩個線程都會得到 1000
,并且給它加上 1
,得到 1001
。接下來,這兩個線程都會把 1001
寫回到內存里去。這樣最終 x
的值就是 1001
。然而,實際上,每個線程都給 x
增加了 1
,現在 x
應該是 1002
才對。要改正這種錯誤,就要用到線程的同步機制。
線程的同步可以使用 Lock
類實現,這個類存在于 Thread
模塊中。這個鎖是用由操作統提供的信號量實現的。信號量是操作系統級別的同步對象,用來控制資源和數據在多處理器程多線程中的訪問。Lock
類提供了兩個方法,acquire
和 release
,詳見如下描述:
-
acquire
方法用來獲取鎖。鎖可以是阻塞式的(默認)或非阻塞式的。如果是一個阻塞鎖,調用acquire
的線程會被阻塞,直到另外的擁有鎖的線程調用release
方法。一旦擁有鎖的線程調用了release
方法,就會有一個調用acquire
的錢程獲得鎖。如果鎖是非阻塞式的,線程在調用acquire
后不會被阻塞,如果鎖沒有鎖,那么線程就會擁有鎖;如果鎖已經被占有,那么acquire
就會返回False
; -
release
方法用來釋放鎖,意味著把鎖重置為非鎖狀態。鎖被重置為非鎖狀態后,如果有很多線程在等待獲取鎖,那么也只有一個線程可以獲得鎖。
下面的代碼演示了在 x
的增加語句前后使用鎖:
from threading import Thread, Lockdef inc_with_lock(lock):global xfor _ in range(1000000):lock.acquire()x += 1lock.release()# 全局變量
x = 0
mylock = Lock()# 創建線程
t1 = Thread(target=inc_with_lock, args=(mylock,), name="線程 1")
t2 = Thread(target=inc_with_lock, args=(mylock,), name="線程 2")# 啟動線程
t1.start()
t2.start()# 等待線程結束
t1.join()
t2.join()print("最后 x 的值是:", x)
以上代碼的輸出結果是:
最后 x 的值是: 2000000
使用鎖后,x
的值就會一直是 2000000
。·lock
對象確保任一時間,只有一個線程操作 x
。
<完>