簡體   English   中英

Python 多線程 Race Condition 行為更改為 function 的時間

[英]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=COUNTERCOUNTER=x+1位於相鄰行時,盡管它們在技術上不是原子的,但調度程序可能會選擇一起執行它們,因為它們是連續的指令。 但是如果我在這兩個語句之間放置一些其他指令,比如print(x) ,由於系統調用,執行肯定不再是原子的,因此代碼做“壞”事情的可能性更高(閱讀:Data Race condition ).

另請閱讀: What is a race condition?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM