[英]Python Multi-threading Race Condition behavior changes as a function of time
我在 Python Multi-Threading 中閱讀了有關競爭條件的內容,並遇到了說明相同情況的代碼片段。
當我嘗試執行它們時,行為(即 output 值)更改為 function 的時間。
我在 Jupyter Notebook 中運行代碼。 這是來自 PyCon 2020 的教程。
代碼片段(我將它們放在不同的塊中,相當於我在筆記本的不同單元格中運行它們):
import threading, time, random
COUNTER = 0
def increement(n):
global COUNTER # Use the global variable declared above
for _ in range(n):
COUNTER += 1
time.sleep(0.001)
print( f"{threading.current_thread().name} finished counting")
ITERATIONS = 10000 # set number of iterations to increase counter
# create 10 threads each set to run the increement() function
threads = [threading.Thread(target=increement, args=(ITERATIONS,) ) for _ in range(10) ]
[t.start() for t in threads]
assert COUNTER == (len(threads) * ITERATIONS), f"Invalid value for counter: {COUNTER}, expected value: {len(threads) * ITERATIONS} "
COUNTER
在運行最后 2 個單元之間的 5 秒與 10 秒差距的值似乎是 100,000 與 200,000。 在最后一個單元格中,即使運行print
function 與assert
語句之間的時間間隔很小,也會導致值發生變化。
我的猜測是執行increment()
的線程比其他線程(執行筆記本單元)運行的時間更長。 但我很想聽聽社區的解釋和觀點(以及對我的任何建議)。
作為后續問題,如果我要在腳本中執行類似的代碼,我如何確保線程在繼續進一步處理COUNTER
變量之前完全完成執行? 我知道threading.Thread.join()
但是當我在啟動線程后調用它時,它們都按順序執行(如下面的代碼片段)。 我想知道的是,是否有一種方法可以等到所有線程並發執行完畢,然后再處理COUNTER
?
# Using join() this way results in all threads getting sequentially executed
for t in threads:
t.start()
t.join()
提前致謝!
(基於進一步挖掘,這是我發現的。如果我錯了,請糾正我)。
我想知道是否有一種方法可以等到所有線程並發執行完后再處理 COUNTER?
這可以通過以下代碼實現。 通過在單獨的循環中執行t.join()
,我們通過確保其他線程立即一個接一個地啟動來防止順序執行。
[ t.start() for t in threads]
[ t.join() for t in threads]
即使進行了此更新, COUNTER
的值仍將等於len(threads)*ITERATIONS
,因為更新操作在上面的 function 中以原子方式發生。
與其他語言相比,Python 中的多線程具有某些怪癖。 Python 有一種稱為全局解釋器鎖 (GIL)的東西,它可以防止兩個或多個線程同時訪問共享變量/內存。
在有問題的函數代碼中,變量的更新發生在單個語句中(即原子地),從而防止其他線程在一個線程寫入它時訪問它。
但是,如果最終目標是使用 Python(盡管存在 GIL)來演示 Race Condition 的想法,則可以將 function 代碼修改如下:
def increement(n):
global COUNTER # Use the global variable declared above
for _ in range(n):
x = COUNTER
COUNTER = x + 1
time.sleep(0.001)
print( f"{threading.current_thread().name} finished counting")
這樣, COUNTER
不再是原子修改,而是分兩步修改,從而允許多個線程讀取COUNTER
的過時值。
編輯:當操作x=COUNTER
和COUNTER=x+1
位於相鄰行時,盡管它們在技術上不是原子的,但調度程序可能會選擇一起執行它們,因為它們是連續的指令。 但是如果我在這兩個語句之間放置一些其他指令,比如print(x)
,由於系統調用,執行肯定不再是原子的,因此代碼做“壞”事情的可能性更高(閱讀:Data Race condition ).
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.