Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/email/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 为什么使用多线程来获得总和是正确的?_Python - Fatal编程技术网

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

Python 为什么使用多线程来获得总和是正确的?,python,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()

我的代码是

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
因为我不使用锁来保护共享资源,即计数器变量,我希望结果是一个小于1000的数字,但计数器总是1000,我不知道为什么。在Python中,
counter+=1
是原子操作吗


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

不要指望
x+=1
是线程安全的。这是它不起作用的地方(见Josiah Carlson的评论):


如果您反汇编
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\u GLOBAL
来检索
x
的值,有一个
INPLACE\u ADD
,然后是一个
STORE\u GLOBAL

如果两个线程
连续加载\u GLOBAL
,则它们可能都加载相同的
x
值。然后它们都递增到相同的数字,并存储相同的数字。因此,一个线程的工作会覆盖另一个线程的工作。这不是线程安全的

如您所见,如果程序是线程安全的,
x
的最终值将是2000000,但是您几乎总是得到一个小于2000000的数字


如果添加锁,则会得到“预期”答案:

屈服

2000000

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

for i in range(1000):
    t = threading.Thread(target = worker)
    threads.append(t)
    t.start()

这是因为您的
工作线程
完成得太快了,与生成新线程所需的时间相比,实际上线程之间没有竞争。在上面的Josiah Carlson示例中,每个线程都在
foo
中花费大量时间,这增加了线程冲突的可能性。

因为GIL,我猜具体来说,CPython是这样做的。@Mike使用GIL时什么操作是原子的?很好。我忘了反汇编指令,以确保它们实际上是解释器中的单字节代码调用。我已经删除了这些误导性的评论。为了补充你最后关于为什么这个问题没有出现的猜测:解释器在放弃GIL之前执行了一定数量的字节码操作,在我的脑海中,GIL大约是100。因此工作线程总是会在这个限制内很好地完成,因此看起来是原子线程。
2000000
for i in range(1000):
    t = threading.Thread(target = worker)
    threads.append(t)
    t.start()