• 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的工作原理(省略了执行完协程之后的过程)如下:

    1. 程序启动,需要创建主协程hub
    2. 主协程执行hub.run()函数,里面主要是执行loop.run(),loop中是子协程,相当于执行子协程的run()函数
    3. 切换到子协程,执行Greenlet.run()函数
    4. Greenlet.run()函数中,执行到self._run()函数,即执行该协程的任务,本例中为自己定义的test()函数
    5. 一直执行到sleep(0)语句
    6. 在sleep()函数中保存回调的位置(即保存该协程执行到的地方),调用waiter.get()函数
    7. waiter.get()函数将调用self.hub.switch()切回主协程hub
    8. hub.switch()将调用greenlet.switch()函数:
      1. 如果即将切换的协程未执行过run函数,则执行run函数;
      2. 如果执行过run函数,则调用Waiter.switch()函数接着上次执行的地方执行

    重复以上的过程,直至所有协程任务全部执行完毕

  • 相关阅读:
    高并发 内核优化
    mysql 读写分离
    Jmeter使用入门
    Jenkins+Jmeter+Ant接口持续集成
    Android客户端稳定性测试——Monkey
    SVN客户端项目递归删除.svn目录
    Windows 下 php5+apache+mysql5 手工搭建笔记
    熟悉常用的Linux操作
    C语言文法
    词法分析实验报告
  • 原文地址:https://www.cnblogs.com/eric-nirnava/p/geventsleep.html
Copyright © 2020-2023  润新知