为什么使用多线程来获得总和是正确的?

rem*_*its 5 python

我的代码是

import threading

counter = 0

def worker():
    global counter
    counter += 1

if __name__ == "__main__":
    threads = []
    for i in range(1000):
        t = threading.Thread(target = worker)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()

    print counter
Run Code Online (Sandbox Code Playgroud)

因为我不使用lock来保护共享资源,即计数器变量,我期望结果是一个小于1000的数字,但计数器总是1000,我不知道为什么.counter += 1在Python中是否 是原子操作?

Python中哪些操作使用GIL是原子的?

unu*_*tbu 8

不要指望x += 1是线程安全的.这是一个不起作用的例子(见Josiah Carlson的评论):

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)
Run Code Online (Sandbox Code Playgroud)

如果您拆卸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        
Run Code Online (Sandbox Code Playgroud)

你看到有一个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)
Run Code Online (Sandbox Code Playgroud)

产量

2000000
Run Code Online (Sandbox Code Playgroud)

我认为你发布的代码没有出现问题的原因:

for i in range(1000):
    t = threading.Thread(target = worker)
    threads.append(t)
    t.start()
Run Code Online (Sandbox Code Playgroud)

是因为worker与生成新线程所需的时间相比,你的完成速度非常快,实际上线程之间没有竞争.在上面的Josiah Carlson的例子中,每个线程都花费了大量的时间foo来增加线程冲突的可能性.