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

2024-04-25 05:12:38 发布

您现在位置:Python中文网/ 问答频道 /正文

我读过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)) 

当我使用python2.7.15时,它返回的结果与本文相同。但是当我使用python3.6.9(所有线程都返回相同的结果=200000)时,情况并非如此。你知道吗

我想知道GIL的新实现(自python3.2以来)是否处理了竞争条件问题?如果是这样的话,为什么在Python>;3.2中仍然存在Lock、Mutex呢。如果没有,为什么在运行多线程来修改共享资源时没有冲突呢?你知道吗

这些天来,当我试图更多地理解Python在引擎盖下是如何工作的时候,我的大脑一直在为这些问题而挣扎。你知道吗


Tags: fortaskmaindeffunction条件variableglobal
2条回答

GIL保护单个字节码指令。相反,竞争条件是incorrect ordering of instructions,这意味着多个字节码指令。因此,GIL不能保护Python VM本身之外的竞争条件。你知道吗


然而,就其本质而言,种族条件并不总是触发的。某些GIL策略或多或少会触发某些比赛条件。比GIL窗口短的线程从不中断,比GIL窗口长的线程总是中断。你知道吗

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

使用旧的GIL每100条指令切换一次,循环可以保证每8.3次调用中断一次,即大约1200次。新的GIL每5ms切换一次,环路只中断3次。你知道吗

您所指的更改是将check interval替换为switch interval。这意味着,它不是每100字节代码切换一次线程,而是每5毫秒切换一次线程。你知道吗

参考号:https://pymotw.com/3/sys/threads.htmlhttps://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中,这总是因为检查间隔设置得太高,以至于每个线程在下一次运行之前都会完整地完成。同样可以用开关间隔来构造。你知道吗

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

相关问题 更多 >