我現在使用 python 大約一年了,對它相當熟悉。雖然我對執行緒很陌生,并且對資料執行緒共享的內容有點困惑。
我一直在網上閱讀所有似乎都同意執行緒共享相同記憶體空間的東西。盡管試圖向自己展示這一點,但我似乎對這種共享的作業方式有一個錯誤的理解。
我寫了一個簡短的腳本,只需將一個添加到區域變數中 3 次。我使用相同的函式一次創建兩個執行緒。我原以為由于共享記憶體,一個執行緒中的 X 變數也會在它休眠時增加,因為另一個執行緒增加了它自己的 X,反之亦然。因此,在執行緒二睡眠時 x=2 的執行緒一的第二個回圈之后,我會認為執行緒二會以 x = 2 而不是 x = 1 從睡眠中出來。雖然正如列印陳述句的順序所暗示的那樣,變數不在執行緒之間共享。
我的問題是,如果您有多個執行緒使用相同的函式一次運行,每個執行緒中的變數是否會在整個程式運行期間每次都保持分開(假設沒有定義全域變數)?那么這個共享記憶體到底是什么意思呢?
任何有關該問題的指導(或一般執行緒建議)將不勝感激。
import threading
from time import sleep
def increase(x):
for i in range(3):
print(f"[{threading.currentThread().getName()}] X is {x}")
x = 1
print(f"[{threading.currentThread().getName()}] X is now {x} after increase")
sleep(0.5)
print(f"[{threading.currentThread().getName()}] X is now {x} after sleep")
return x
def main():
x = 0
first = threading.Thread(name="Thread One", target=increase,args=([x]))
second = threading.Thread(name="Thread Two", target=increase,args=([x]))
first.start()
second.start()
first.join()
second.join()
if __name__ == "__main__":
main()
結果是:
[Thread One] X is 0
[Thread One] X is now 1 after increase
[Thread Two] X is 0
[Thread Two] X is now 1 after increase
[Thread Two] X is now 1 after sleep[Thread One] X is now 1 after sleep
[Thread One] X is 1
[Thread One] X is now 2 after increase
[Thread Two] X is 1
[Thread Two] X is now 2 after increase
[Thread One] X is now 2 after sleep[Thread Two] X is now 2 after sleep
[Thread Two] X is 2
[Thread Two] X is now 3 after increase
[Thread One] X is 2
[Thread One] X is now 3 after increase
[Thread One] X is now 3 after sleep[Thread Two] X is now 3 after sleep
uj5u.com熱心網友回復:
你說記憶體是共享的是正確的,但它的復雜性更深。您感到困惑的是不可變型別與可變型別。您可以在此處了解更多資訊。我已經洗掉了 for 回圈,因為它變得令人困惑:
import threading
from time import sleep
def increase(x):
print(f"[{threading.currentThread().getName()}] address of x: {hex(id(x))} ")
print(f"[{threading.currentThread().getName()}] X is {x}")
x = 1
print(f"[{threading.currentThread().getName()}] address of x after increment: {hex(id(x))} ")
print(f"[{threading.currentThread().getName()}] X is now {x} after increase")
sleep(0.5)
print(f"[{threading.currentThread().getName()}] X is now {x} after sleep")
print(f"[{threading.currentThread().getName()}] address of x after sleep: {hex(id(x))} ")
return x
def main():
x = 0
first = threading.Thread(name="Thread One", target=increase, args=([x]))
second = threading.Thread(name="Thread Two", target=increase, args=([x]))
first.start()
second.start()
first.join()
second.join()
if __name__ == "__main__":
main()
我在這里所做的是列印x執行緒中的地址。輸出:
[Thread One] address of x: 0x7ffbbebb7c20
[Thread One] X is 0
[Thread One] address of x after increment: 0x7ffbbebb7c40
[Thread One] X is now 1 after increase
[Thread Two] address of x: 0x7ffbbebb7c20
[Thread Two] X is 0
[Thread Two] address of x after increment: 0x7ffbbebb7c40
[Thread Two] X is now 1 after increase
[Thread Two] X is now 1 after sleep[Thread One] X is now 1 after sleep
[Thread One] address of x after sleep: 0x7ffbbebb7c40
[Thread Two] address of x after sleep: 0x7ffbbebb7c40
您會注意到,當我只是讀取 x 地址時,第一行列印是0x7ffbbebb7c20在更新它之后執行緒 1 和 2 得到不同的地址:0x7ffbbebb7c40。現在它們都獲得了相同的地址,因為 python 試圖降低記憶體占用。您可以在此處找到有關此內容的更多資訊但出于我們的目的,該函式會讀取相同的變數,并且一旦您嘗試寫入或更新該變數,就會為該執行緒制作它的副本。僅當您使用不可變型別(int、string、instances 等)時,如果您傳遞像 dict 這樣的可變型別,只會發生這種情況:
import threading
from time import sleep
def increase(test_var):
print(f"[{threading.currentThread().getName()}] Address of test_var: {hex(id(test_var))}")
print(f"[{threading.currentThread().getName()}] Address of test_var['key']: {hex(id(test_var['key']))}")
print(f"[{threading.currentThread().getName()}] test_var['key'] is {test_var['key']}")
test_var['key'] = 1
print(f"[{threading.currentThread().getName()}] test_var['key'] is now {test_var['key']} after increase")
print(f"[{threading.currentThread().getName()}] Address of test_var after increment: {hex(id(test_var))}")
print(f"[{threading.currentThread().getName()}] Address of test_var['key'] after increment: {hex(id(test_var['key']))}")
sleep(0.5)
print(f"[{threading.currentThread().getName()}] test_var['key'] is now {test_var['key']} after sleep")
print(f"[{threading.currentThread().getName()}] Address of test_var after sleep: {hex(id(test_var))}")
print(f"[{threading.currentThread().getName()}] Address of test_var['key'] after sleep: {hex(id(test_var['key']))}")
return test_var
def main():
test_var = {'key': 0}
first = threading.Thread(name="Thread One", target=increase, args=([test_var]))
second = threading.Thread(name="Thread Two", target=increase, args=([test_var]))
first.start()
second.start()
first.join()
second.join()
if __name__ == "__main__":
main()
輸出是您所期望的:
[Thread One] Address of test_var: 0x22216509a98
[Thread One] Address of test_var['key']: 0x7ffbaf7d7c20
[Thread One] test_var['key'] is 0
[Thread One] test_var['key'] is now 1 after increase
[Thread One] Address of test_var after increment: 0x22216509a98
[Thread One] Address of test_var['key'] after increment: 0x7ffbaf7d7c40
[Thread Two] Address of test_var: 0x22216509a98
[Thread Two] Address of test_var['key']: 0x7ffbaf7d7c40
[Thread Two] test_var['key'] is 1
[Thread Two] test_var['key'] is now 2 after increase
[Thread Two] Address of test_var after increment: 0x22216509a98
[Thread Two] Address of test_var['key'] after increment: 0x7ffbaf7d7c60
[Thread Two] test_var['key'] is now 2 after sleep
[Thread Two] Address of test_var after sleep: 0x22216509a98
[Thread Two] Address of test_var['key'] after sleep: 0x7ffbaf7d7c60
[Thread One] test_var['key'] is now 2 after sleep
[Thread One] Address of test_var after sleep: 0x22216509a98
[Thread One] Address of test_var['key'] after sleep: 0x7ffbaf7d7c60
注意 test_var ( 0x22216509a98) 的地址在執行緒之間是如何變化的,因為它是可變的并且可以跨執行緒共享。
uj5u.com熱心網友回復:
在您的情況下, x 由函式引數共享為副本而不是參考。如果你想增加你的計數器,你必須將它封裝在一個類中。
例如:
import threading
from time import sleep
class foo:
x = 0
def increase(foo):
for i in range(3):
print(f"[{threading.currentThread().getName()}] X is {foo.x}")
foo.x = 1
print(f"[{threading.currentThread().getName()}] X is now {foo.x} after increase")
sleep(0.5)
print(f"[{threading.currentThread().getName()}] X is now {foo.x} after sleep")
return foo.x
def main():
x = foo()
first = threading.Thread(name="Thread One", target=increase,args=([x]))
second = threading.Thread(name="Thread Two", target=increase,args=([x]))
first.start()
second.start()
first.join()
second.join()
if __name__ == "__main__":
main()
注意:Python 執行緒是特定的。你可以看看這個視頻https://www.youtube.com/watch?v=Obt-vMVdM8s
- - - - - - 編輯 - - - - - - -
更準確地說。在您的情況下,x是一個int,因此在每個函式呼叫時都會復制它。無論是string還是float都具有相同的行為。
沒有執行緒,您將有相同的行為:
def increase(x):
for i in range(3):
print(x)
x = 1
return x
x = 0
increase(x)
assert x == 0
x = 1
increase(x)
assert x == 1
uj5u.com熱心網友回復:
您接受的答案并沒有直接回答這個問題:
如果您有多個執行緒使用同一個函式同時運行,每個執行緒中的變數是否會保持分開?
“本地”不僅意味著此函式的本地,還意味著此函式呼叫的本地。
函式的引數和區域變數的值存盤在激活記錄中。每次呼叫函式時,都會創建一個新的激活記錄,當函式回傳時,該激活記錄會被銷毀。
這意味著,函式中的x引數在每次呼叫increase(x)函式時都是不同的變數。如果一個函式遞回呼叫自己,那么每次遞回呼叫中args和locals是不同的變數,如果函式在多個執行緒中被呼叫,那么args和locals在每個執行緒中都是不同的變數。
我一直在網上閱讀所有似乎都同意執行緒共享相同記憶體空間的東西。
絕對正確,但引數或本地不是記憶體中的確定位置。全域是記憶體中的確定位置。所以,如果你有一些global g,每個執行緒都會同意g具有相同的值。而且,一個 Python 物件,只要它存在,就占據一個確定的位置,所以每個參考同一物件的執行緒都會看到它處于相同的狀態。但是,區域變數在每次激活宣告它的函式時占用不同的記憶體位置。
區域變數和引數不共享。它們不會在對函式的遞回呼叫之間共享,也不會由來自不同執行緒的呼叫共享。
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/481894.html
