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()