sleep函数——Gevent源码分析
gevent是一个异步I/O框架,当遇到I/O操作的时候,会自动切换任务,从而能异步地完成I/O操作
但是在测试的情况下,可以使用sleep
函数来让gevent进行任务切换。示例如下:
import gevent
def test(id):
print('Test %s is running...' % id)
gevent.sleep(0)
print('Test %s is done!' % id)
gevent.joinall([gevent.spawn(test, i) for i in range(2)])
该函数的执行结果是:
Test 0 is running...
Test 1 is running...
Test 0 is done!
Test 1 is done!
可见,sleep
函数能让gevent切换协程,进行异步操作。
这次我想探究一下sleep
函数的原理。
在了解sleep函数之前,我们需要了解一下gevent的运行
在前面的文章中,我们知道了gevent有个主协程hub的概念,当需要切换协程的时候,需要先回到hub,然后再由hub去切换。
其实主协程hub是一个特殊的协程Greenlet。
当gevent运行的时候,gevent需要先创建一个主协程hub,并运行hub的run函数(具体源码在hub.py/run),比较简单,核心代码是loop.run()
,这个run函数是Greenlet类中的run函数,用来切入loop中的子协程,源码在greenlet.py/run
中。核心就是result = self._run(*self.args, **self.kwargs)
, _run
函数用来执行这个子协程的任务
sleep函数
在刚刚的示例代码中,在sleep处设置断点,进行跟踪。
首先,进入sleep函数,函数在hub.py
中:
def sleep(seconds=0, ref=True):
hub = get_hub() #获得主协程hub对象
loop = hub.loop #获得主循环
if seconds <= 0:
waiter = Waiter()
loop.run_callback(waiter.switch) #设置回调函数(即下次本协程执行的地点)
waiter.get()
else:
hub.wait(loop.timer(seconds, ref=ref))
当seconds=0的时候,loop.run_callback(waiter.switch)
把当前greenlet的switch
注册到loop中,设置为回调函数,此时的loop是主协程hub下的loop。
sleep
函数中最后调用了waiter.get()
,get
函数简化如下:
def get(self):
assert self.greenlet is None, 'This Waiter is already used by %r' % (self.greenlet, )
self.greenlet = getcurrent()
try:
return self.hub.switch()
finally:
self.greenlet = None
self.greenlet = getcurrent()
: 把greenlet设置为当前协程greenlet
return self.hub.switch()
: 切换到主线程hub的主循环, 然后主循环再切换到下一个greenlet协程
工作流程如图:
总结
Gevent的工作原理(省略了执行完协程之后的过程)如下:
- 程序启动,需要创建主协程hub
- 主协程执行
hub.run()
函数,里面主要是执行loop.run()
,loop中是子协程,相当于执行子协程的run()函数 - 切换到子协程,执行
Greenlet.run()
函数 - 在
Greenlet.run()
函数中,执行到self._run()
函数,即执行该协程的任务,本例中为自己定义的test()
函数 - 一直执行到sleep(0)语句
- 在sleep()函数中保存回调的位置(即保存该协程执行到的地方),调用
waiter.get()
函数 waiter.get()
函数将调用self.hub.switch()
切回主协程hubhub.switch()
将调用greenlet.switch()
函数:- 如果即将切换的协程未执行过run函数,则执行run函数;
- 如果执行过run函数,则调用
Waiter.switch()
函数接着上次执行的地方执行
重复以上的过程,直至所有协程任务全部执行完毕