【问题标题】:Multithreading in python2 vs python 3python2与python 3中的多线程
【发布时间】:2019-07-28 22:51:37
【问题描述】:

我熟悉 Python 的 GIL,所以我知道多线程在 Python 中并不是真正的多线程。

当我运行下面的代码时,我预计结果会是 0,因为 GIL 不允许存在竞争条件。在python3中,结果为0。但在python2中,结果不是0;结果是一个意想不到的结果,例如 -3492 或 21283。

我该如何解决这个问题?

import threading 
x = 0 # A shared value

def foo(): 
  global x 
  for i in range(100000000): 
    x += 1 

def bar(): 
  global x 
  for i in range(100000000):
    x -= 1 

t1 = threading.Thread(target=foo) 
t2 = threading.Thread(target=bar) 

t1.start() 
t2.start() 

t1.join() 
t2.join() # Wait for completion

print(x)

【问题讨论】:

  • 我强烈建议不要有人运行它;它可能会锁定你的机器。

标签: python python-3.x multithreading python-2.x gil


【解决方案1】:

x += 1 语句在任何 Python 版本中都不是线程安全的。您在 Python 2 而不是 Python 3 中看到竞争条件的结果这一事实主要只是巧合(它可能与 GIL 在线程之间切换时的优化有关,但我不知道细节) .它也可能在 Python 3 中得到错误的结果。

原因是+= 运算符不是原子的。它需要多个字节码来运行,而 GIL 只保证在任何一个字节码运行时防止线程之间的切换。让我们看看你的 foo 函数的反汇编,看看它是如何工作的(这是来自 Python 3.7,在 Python 2.7 中字节码中的地址不同,但所有操作都是相同的):

>>> dis.dis(foo)
  3           0 SETUP_LOOP              24 (to 26)
              2 LOAD_GLOBAL              0 (range)
              4 LOAD_CONST               1 (100000000)
              6 CALL_FUNCTION            1
              8 GET_ITER
        >>   10 FOR_ITER                12 (to 24)
             12 STORE_FAST               0 (i)

  4          14 LOAD_GLOBAL              1 (x)
             16 LOAD_CONST               2 (1)
             18 INPLACE_ADD
             20 STORE_GLOBAL             1 (x)
             22 JUMP_ABSOLUTE           10
        >>   24 POP_BLOCK
        >>   26 LOAD_CONST               0 (None)
             28 RETURN_VALUE

我们关心的是字节码位置为 14-20 的四行。前两个将参数加载到加法中。第三个执行INPLACE_ADD 操作。加法的结果被放回堆栈,因为并非所有类型的对象都可以就地更新(整数不能,所以这里有必要)。最后一个字节码将总和存储回原始名称。

如果解释器选择在我们在字节码 14 中加载 x 和在字节码 20 中再次将新值存储到它时切换哪个线程保存 GIL,我们可能会得到一个不正确的结果,因为当我们再次获得 GIL 时,我们之前加载的值可能不再有效。

正如我上面提到的,您在 Python 3 中得到 0 的事实仅仅是实现细节的结果,解释器选择在您测试字节码的关键部分期间不切换。如果您在另一种情况下(例如在 CPU 负载较重的情况下)或在不同的解释器版本(例如 3.7 而不是 3.6 或其他)中再次运行程序,无法保证它不会做出不同的选择。

如果你想要真正的线程安全,那么你应该使用真正的锁,而不是仅仅依赖 GIL。 GIL 仅确保解释器的内部状态保持正常。它不能保证你的每一行代码都是原子的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-26
    • 2021-06-26
    • 2020-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多