Python中GIL的新实现是否处理了竞争条件问题?

我已经读过an article的有关Python中多线程的知识,他们尝试使用同步来解决竞争条件问题。而且我运行了以下示例代码来重现竞态条件问题:

import threading 

# global variable x 
x = 0

def increment(): 
    """ 
    function to increment global variable x 
    """
    global x 
    x += 1

def thread_task(): 
    """ 
    task for thread 
    calls increment function 100000 times. 
    """
    for _ in range(100000): 
        increment() 

def main_task(): 
    global x 
    # setting global variable x as 0 
    x = 0

    # creating threads 
    t1 = threading.Thread(target=thread_task) 
    t2 = threading.Thread(target=thread_task) 

    # start threads 
    t1.start() 
    t2.start() 

    # wait until threads finish their job 
    t1.join() 
    t2.join() 

if __name__ == "__main__": 
    for i in range(10): 
        main_task() 
        print("Iteration {0}: x = {1}".format(i,x)) 

当我使用Python 2.7.15时,它确实返回与文章相同的结果。但是当我使用Python 3.6.9时却没有(所有线程返回相同的结果= 200000)。

我想知道GIL的新实现(自Python 3.2起)是否处理​​了竞争条件问题?如果可以,为什么在Python> 3.2中仍然存在Lock,Mutex。如果没有,为什么在运行多线程以修改共享资源(如上例)时没有冲突?

这些天,当我试图更多地了解Python到底如何工作时,我的想法一直困扰着我们。

cdau9874 回答:Python中GIL的新实现是否处理了竞争条件问题?

您要更改的是用开关间隔代替检查间隔。这意味着与其每100字节代码切换一次线程,不如每5毫秒切换一次线程。

参考:https://pymotw.com/3/sys/threads.html https://mail.python.org/pipermail/python-dev/2009-October/093321.html

因此,如果您的代码运行速度足够快,它将永远不会经历线程切换,并且在您看来操作实际上是原子操作时,您可能会发现它们是原子操作。由于没有实际的线程交织,因此没有出现竞争条件。 x += 1实际上是四个字节的代码:

>>> dis.dis(sync.increment)
 11           0 LOAD_GLOBAL              0 (x)
              3 LOAD_CONST               1 (1)
              6 INPLACE_ADD         
              7 STORE_GLOBAL             0 (x)
             10 LOAD_CONST               2 (None)
             13 RETURN_VALUE        

解释器中的线程切换可以发生在任意两个字节码之间。

请考虑在2.7中始终打印200000,因为检查间隔设置得太高,以至于每个线程在下一次运行之前就完整地完成了。可以用切换间隔来构造相同的对象。

import sys
import threading 

print(sys.getcheckinterval())
sys.setcheckinterval(1000000)

# global variable x 
x = 0

def increment(): 
    """ 
    function to increment global variable x 
    """
    global x 
    x += 1

def thread_task(): 
    """ 
    task for thread 
    calls increment function 100000 times. 
    """
    for _ in range(100000): 
        increment() 

def main_task(): 
    global x 
    # setting global variable x as 0 
    x = 0

    # creating threads 
    t1 = threading.Thread(target=thread_task) 
    t2 = threading.Thread(target=thread_task) 

    # start threads 
    t1.start() 
    t2.start() 

    # wait until threads finish their job 
    t1.join() 
    t2.join() 

if __name__ == "__main__": 
    for i in range(10): 
        main_task() 
        print("Iteration {0}: x = {1}".format(i,x)) 
,

GIL保护单个字节码指令。相反,竞争条件是incorrect ordering of instructions,表示 multiple 个字节代码指令。结果,GIL 无法防止Python VM本身以外的竞争情况。


但是,就其本质而言,种族状况并不总是会触发。某些GIL策略或多或少会触发某些竞赛条件。比GIL窗口短的线程永远不会中断,而比GIL窗口长的线程总是会中断。

您的increment函数具有6个字节的代码指令,内部循环调用它也是如此。其中,必须立即完成4条指令,这意味着有3个可能的切换点会破坏结果。您的整个thread_task函数大约需要0.015到0.020s(在我的系统上)。

使用旧的GIL每100条指令切换一次,可以保证该循环每8.3次调用(约1200次)被中断。新的GIL每5毫秒切换一次,循环仅中断3次。

本文链接:https://www.f2er.com/2881151.html

大家都在问