不要指望x += 1
是線程安全的。這裏是an example它不工作(見約西亞卡爾森的評論):
import threading
x = 0
def foo():
global x
for i in xrange(1000000):
x += 1
threads = [threading.Thread(target=foo), threading.Thread(target=foo)]
for t in threads:
t.daemon = True
t.start()
for t in threads:
t.join()
print(x)
如果你拆開foo
:
In [80]: import dis
In [81]: dis.dis(foo)
4 0 SETUP_LOOP 30 (to 33)
3 LOAD_GLOBAL 0 (xrange)
6 LOAD_CONST 1 (1000000)
9 CALL_FUNCTION 1
12 GET_ITER
>> 13 FOR_ITER 16 (to 32)
16 STORE_FAST 0 (i)
5 19 LOAD_GLOBAL 1 (x)
22 LOAD_CONST 2 (1)
25 INPLACE_ADD
26 STORE_GLOBAL 1 (x)
29 JUMP_ABSOLUTE 13
>> 32 POP_BLOCK
>> 33 LOAD_CONST 0 (None)
36 RETURN_VALUE
你看,有一個LOAD_GLOBAL
檢索的x
值,有一個INPLACE_ADD
,然後是一個STORE_GLOBAL
。
如果兩個線程LOAD_GLOBAL
相繼,那麼它們可能都會加載相同的值x
。然後他們都增加到相同的數字,並存儲相同的數字。所以一個線程的工作會覆蓋另一個線程的工作。這不是線程安全的。
正如你所看到的,x
的最終值是2000000,如果程序是線程安全的,而是你幾乎總是得到一個數小於2000000
如果添加了鎖,你得到 「預期」 的答案:
import threading
lock = threading.Lock()
x = 0
def foo():
global x
for i in xrange(1000000):
with lock:
x += 1
threads = [threading.Thread(target=foo), threading.Thread(target=foo)]
for t in threads:
t.daemon = True
t.start()
for t in threads:
t.join()
print(x)
產生
2000000
我想,爲什麼你發佈的代碼不呈現問題的原因:
for i in range(1000):
t = threading.Thread(target = worker)
threads.append(t)
t.start()
是因爲你的worker
氏完全這麼混賬的迅速相比,它需要產生一個新的線程,在實踐中存在的時間線程之間沒有競爭。在上面的Josiah Carlson的例子中,每個線程在foo
中花費了大量的時間,這增加了線程碰撞的機會。
由於GIL我猜 – wim 2013-02-25 01:53:44
特別是,CPython的確如此。 – Amber 2013-02-25 01:54:39
@Mike什麼操作是原子使用GIL時? – remykits 2013-02-25 01:59:12