python线程安全的计数器_+ =运算符在Python中是线程安全的吗?
+ =運算符在Python中是線程安全的嗎?
我想為實驗創建一個非線程安全的代碼塊,這些是2個線程將要調用的函數。
c = 0
def increment():
c += 1
def decrement():
c -= 1
此代碼線程安全嗎?
如果不是,我可以理解為什么它不是線程安全的,以及哪種類型的語句通常會導致非線程安全的操作。
如果它是線程安全的,如何使它顯式為非線程安全的?
8個解決方案
88 votes
不,該代碼絕對是絕對線程安全的。
import threading
i = 0
def test():
global i
for x in range(100000):
i += 1
threads = [threading.Thread(target=test) for t in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
assert i == 1000000, i
持續失敗。
i + = 1解析為四個操作碼:加載i,加載1,將兩者相加,然后將其存儲回i。 Python解釋器每100個操作碼切換一次活動線程(通過從一個線程釋放GIL,以便另一個線程可以擁有它)。 (這兩個都是實現細節。)當在加載和存儲之間發生100操作碼搶占時,就會發生競爭狀態,從而允許另一個線程開始遞增計數器。 當它返回到掛起的線程時,它將繼續使用舊值“ i”,并且同時撤消其他線程運行的增量。
使它成為線程安全的很簡單。 添加鎖:
#!/usr/bin/python
import threading
i = 0
i_lock = threading.Lock()
def test():
global i
i_lock.acquire()
try:
for x in range(100000):
i += 1
finally:
i_lock.release()
threads = [threading.Thread(target=test) for t in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
assert i == 1000000, i
Glenn Maynard answered 2020-07-27T15:56:46Z
28 votes
(注意:每個函數都需要inc才能使代碼正常工作。)
此代碼線程安全嗎?
否。在CPython中,只有一個字節碼指令是“原子的”,即使所涉及的值是簡單的整數,inc也可能不會產生單個操作碼:
>>> c= 0
>>> def inc():
... global c
... c+= 1
>>> import dis
>>> dis.dis(inc)
3 0 LOAD_GLOBAL 0 (c)
3 LOAD_CONST 1 (1)
6 INPLACE_ADD
7 STORE_GLOBAL 0 (c)
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
因此,一個線程可以在裝入c和1的情況下到達索引6,放棄GIL并讓另一個線程進入,該線程執行inc并進入睡眠狀態,將GIL返回到第一個線程,該線程現在具有錯誤的值。
無論如何,原子性是您不應該依賴的實現細節。 字節碼在將來的CPython版本中可能會更改,并且在不依賴GIL的其他Python實現中,結果將完全不同。 如果需要線程安全,則需要鎖定機制。
bobince answered 2020-07-27T15:57:24Z
15 votes
確保我建議使用鎖:
import threading
class ThreadSafeCounter():
def __init__(self):
self.lock = threading.Lock()
self.counter=0
def increment(self):
with self.lock:
self.counter+=1
def decrement(self):
with self.lock:
self.counter-=1
同步裝飾器還可以幫助使代碼易于閱讀。
gillesv answered 2020-07-27T15:57:48Z
10 votes
很容易證明您的代碼不是線程安全的。 您可以通過在關鍵部分使用睡眠來提高查看比賽狀況的可能性(這只是模擬CPU速度慢)。 但是,如果您將代碼運行足夠長的時間,無論如何最終都應該看到競爭狀態。
from time import sleep
c = 0
def increment():
global c
c_ = c
sleep(0.1)
c = c_ + 1
def decrement():
global c
c_ = c
sleep(0.1)
c = c_ - 1
John La Rooy answered 2020-07-27T15:58:10Z
4 votes
簡短的回答:不。
長答案:一般不會。
盡管CPython的GIL使單個操作碼具有線程安全性,但這不是一般行為。 您可能不會假設,即使簡單的操作(例如加法)也是原子指令。 當另一個線程運行時,添加可能僅完成一半。
而且,一旦函數在多個操作碼中訪問一個變量,線程安全性就會消失。 如果將函數體包裝在鎖中,則可以生成線程安全性。 但是請注意,鎖可能會在計算上耗費大量資源并可能產生死鎖。
ebo answered 2020-07-27T15:58:44Z
3 votes
單一操作碼由于具有GIL而具有線程安全性,但除此之外:
import time
class something(object):
def __init__(self,c):
self.c=c
def inc(self):
new = self.c+1
# if the thread is interrupted by another inc() call its result is wrong
time.sleep(0.001) # sleep makes the os continue another thread
self.c = new
x = something(0)
import threading
for _ in range(10000):
threading.Thread(target=x.inc).start()
print x.c # ~900 here, instead of 10000
多個線程共享的每個資源都必須有一個鎖。
Jochen Ritzel answered 2020-07-27T15:56:12Z
2 votes
如果您實際上想使代碼不是線程安全的,并且很有可能在不嘗試一萬次的情況下(或在您真正不希望發生“壞的”事情發生的情況下)就發生“壞的”事情, 您可以通過顯式睡眠來“抖動”您的代碼:
def íncrement():
global c
x = c
from time import sleep
sleep(0.1)
c = x + 1
Rasmus Kaj answered 2020-07-27T15:59:04Z
0 votes
您確定函數遞增和遞減執行沒有任何錯誤嗎?
我認為它應該引發UnboundLocalError,因為您必須明確地告訴Python您要使用名為'c'的全局變量。
因此,將遞增(也遞減)更改為以下內容:
def increment():
global c
c += 1
我認為您的代碼也是線程不安全的。 有關Python中的線程同步機制的本文可能會有所幫助。
ardsrk answered 2020-07-27T15:59:37Z
總結
以上是生活随笔為你收集整理的python线程安全的计数器_+ =运算符在Python中是线程安全的吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c++ 正则表达式_Java入门 - 语
- 下一篇: 【ES6(2015)】Proxy