gevent monkey打补丁的Queue.put是否产生上下文切换? 更新

问题描述

import gevent.monkey
gevent.monkey.patch_all()
import gevent
from queue import Queue
import random
import time


def getter(q):
    while True:
        print('getting')
        v = q.get()
        print(f'got {v}')

def putter(q):
    while True:
        print(f'start putting')
        v = int(random.random() * 1000)
        # `put_Nowait` also seems to yield
        # q.put(v)
        q.put_Nowait(v)
        print(f'done putting with {v}')

        if random.random() < 0.5:
            print(f'yield')
            time.sleep(0)


queue = Queue()


gevent.spawn(getter,queue)
gevent.spawn(putter,queue)

time.sleep(1000)

我使用queue.put还是queue.put_Nowait都没关系,我看到了类似的日志

# start putting
# got 25
# getting
# done putting with 535

建议gevent每次执行queue.put时是否可以进行上下文切换?

更新

修改了一些代码

flag = True

def getter(q):
    while True:
        print('getting')
        global flag
        flag = False
        v = q.get()
        print(f'got {v}')

def putter(q):
    v = 0
    while True:
        print(f'start putting {v}')
        global flag
        flag = True
        # `put_Nowait` also seems to yield
        # q.put(v)
        q.put_Nowait(v)
        if not flag:
            raise Exception('yield happened')

        print(f'done putting with {v}')
        v += 1

        time.sleep(0)
        # If I sleep with non-zero,the above seems to only yield once at the beginning.
        # time.sleep(0.000001)


queue = Queue()


def myTracer(event,args):
    src,target = args
    if event == "switch":
        # print("from %s switch to %s" % (src,target))
        # Print to stdout like the rest of the code. Otherwise the order of stdout & stderr is not guaranteed.
        traceback.print_stack(file=sys.stdout)
    elif event == "throw":
        print("from %s throw exception to %s" % (src,target))


# greenlet.settrace(myTracer)

gevent.spawn(getter,queue)
putter(queue)

使用修补的Python队列,

  • 如果我sleep(0),它可能会进行上下文切换。
  • 如果我sleep(0.0000001),它在开始时只产生一次。
  • 如果我根本不睡觉,一开始只会屈服一次,而吸气剂则没有机会再跑。

查看堆栈跟踪,我发现Queue.put调用notify,后者调用lock.acquire(0)。然后对其进行修补,以便在sleep()

调用gevent/thread.py

如果我使用gevent.queue.Queue而不是Python Queue,它似乎并没有进行上下文切换。

解决方法

您可以将greenlet.settrace与回调函数一起使用来检测上下文切换。
将其添加到您的代码中将显示putput_nowait都进行上下文切换。

...

def myTracer(event,args):
    src,target = args
    if event == "switch":
        print("from %s switch to %s" % (src,target))
    elif event == "throw":
        print("from %s throw exception to %s" % (src,target))


greenlet.settrace(myTracer)


queue = Queue()
gevent.spawn(getter,queue)
gevent.spawn(putter,queue)

time.sleep(5)

您将在stdout中看到很多“切换”调试消息:

...
done putting with 106
start putting
from <Greenlet at 0x10418e150: putter(<queue.Queue object at 0x1034d9460>)> switch to <Hub '' at 0x10416a400 select default pending=0 ref=3 thread_ident=0x106d15dc0>
from <Hub '' at 0x10416a400 select default pending=0 ref=1 thread_ident=0x106d15dc0> switch to <Greenlet at 0x10418e150: putter(<queue.Queue object at 0x1034d9460>)>
done putting with 487
start putting
....

编辑/注释:

我正在使用Python3.8和gevent == 20.9.0。在测试时,我删除了整个if条件if random.random() ...,但将stdout传递到文件并搜索getter上下文开关和getting都存在于stdout中。

我没有进一步调查,但是如果您查看source code,您会发现有一个显式调用getter.switch(getter),这可能是导致上下文切换的原因。