Python gevent协程切换实现详解


Posted in Python onSeptember 14, 2020

一、背景

大家都知道gevent的机制是单线程+协程机制,当遇到可能会阻塞的操作时,就切换到可运行的协程中继续运行,以此来实现提交系统运行效率的目标,但是具体是怎么实现的呢?让我们直接从代码中看一下吧。

二、切换机制

让我们从socket的send、recv方法入手:

def recv(self, *args):
  while 1:
    try:
      return self._sock.recv(*args)
    except error as ex:
      if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0:
        raise
      # QQQ without clearing exc_info test__refcount.test_clean_exit fails
      sys.exc_clear()
    self._wait(self._read_event)

这里会开启一个死循环,在循环中调用self._sock.recv()方法,并捕获异常,当错误是EWOULDBLOCK时,则调用self._wait(self._read_event)方法,该方法其实是:_wait = _wait_on_socket,_wait_on_socket方法的定义在文件:_hub_primitives.py中,如下:

# Suitable to be bound as an instance method
def wait_on_socket(socket, watcher, timeout_exc=None):
  if socket is None or watcher is None:
    # test__hub TestCloseSocketWhilePolling, on Python 2; Python 3
    # catches the EBADF differently.
    raise ConcurrentObjectUseError("The socket has already been closed by another greenlet")
  _primitive_wait(watcher, socket.timeout,
          timeout_exc if timeout_exc is not None else _NONE,
          socket.hub)

该方法其实是调用了函数:_primitive_wait(),其仍然在文件:_hub_primitives.py中定义,如下:

def _primitive_wait(watcher, timeout, timeout_exc, hub):
  if watcher.callback is not None:
    raise ConcurrentObjectUseError('This socket is already used by another greenlet: %r'
                    % (watcher.callback, ))

  if hub is None:
    hub = get_hub()

  if timeout is None:
    hub.wait(watcher)
    return

  timeout = Timeout._start_new_or_dummy(
    timeout,
    (timeout_exc
     if timeout_exc is not _NONE or timeout is None
     else _timeout_error('timed out')))

  with timeout:
    hub.wait(watcher)

这里其实是调用了hub.wait()函数,该函数的定义在文件_hub.py中,如下:

class WaitOperationsGreenlet(SwitchOutGreenletWithLoop): # pylint:disable=undefined-variable

  def wait(self, watcher):
    """
    Wait until the *watcher* (which must not be started) is ready.

    The current greenlet will be unscheduled during this time.
    """
    waiter = Waiter(self) # pylint:disable=undefined-variable
    watcher.start(waiter.switch, waiter)
    try:
      result = waiter.get()
      if result is not waiter:
        raise InvalidSwitchError(
          'Invalid switch into %s: got %r (expected %r; waiting on %r with %r)' % (
            getcurrent(), # pylint:disable=undefined-variable
            result,
            waiter,
            self,
            watcher
          )
        )
    finally:
      watcher.stop()

watcher.stop()

该类WaitOperationsGreenlet是Hub的基类,其方法wait中的逻辑是:生成一个Waiter对象,并调用watcher.start(waiter.switch, waiter)方法,watcher是最开始recv方法中使用的self._read_event,watcher是gevent的底层事件框架libev中的概念;同时还有一个waiter对象,它类似与python中的future概念,该对象有一个switch()方法以及get()方法,当没有得到结果没有准备好时,调用waiter.get()方法回导致协程被挂起;get()函数的定义如下:

def get(self):
  """If a value/an exception is stored, return/raise it. Otherwise until switch() or throw() is called."""
  if self._exception is not _NONE:
    if self._exception is None:
      return self.value
    getcurrent().throw(*self._exception) # pylint:disable=undefined-variable
  else:
    if self.greenlet is not None:
      raise ConcurrentObjectUseError('This Waiter is already used by %r' % (self.greenlet, ))
    self.greenlet = getcurrent() # pylint:disable=undefined-variable
    try:
      return self.hub.switch()
    finally:
      self.greenlet = None

在get()中最关键的是self.hub.switch()函数,该函数将执行权转移到hub,并继续运行,至此已经分析完了当在worker协程中从网络获取数据遇到阻塞时,如何避免阻塞并切换到hub中的实现,至于何时再切换会worker协程,我们后续再继续分析。

总结

要记得gevent中一个重要的概念,协程切换不是调用而是执行权的转移,从可能会阻塞的协程切换到hub,并由hub在合适的时机切换到另一个可以继续运行的协程继续执行;gevent通过这种形式实现了提高io密集型应用吞吐率的目标。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python中super的用法实例
May 28 Python
Python使用sftp实现上传和下载功能(实例代码)
Mar 14 Python
详解Python做一个名片管理系统
Mar 14 Python
python实现文本进度条 程序进度条 加载进度条 单行刷新功能
Jul 03 Python
python+opencv像素的加减和加权操作的实现
Jul 14 Python
Python函数参数类型及排序原理总结
Dec 19 Python
Python Opencv 通过轨迹(跟踪)栏实现更改整张图像的背景颜色
Mar 09 Python
python 实时调取摄像头的示例代码
Nov 25 Python
Python读取pdf表格写入excel的方法
Jan 22 Python
python中sys模块的介绍与实例
Apr 17 Python
解决Pytorch半精度浮点型网络训练的问题
May 24 Python
pytorch中的torch.nn.Conv2d()函数图文详解
Feb 28 Python
通过实例了解python__slots__使用方法
Sep 14 #Python
python如何遍历指定路径下所有文件(按按照时间区间检索)
Sep 14 #Python
详解python实现可视化的MD5、sha256哈希加密小工具
Sep 14 #Python
Python利用pip安装tar.gz格式的离线资源包
Sep 14 #Python
Python tkinter制作单机五子棋游戏
Sep 14 #Python
python安装cx_Oracle和wxPython的方法
Sep 14 #Python
python输入中文的实例方法
Sep 14 #Python
You might like
4月1日重磅发布!《星际争霸II》6.0.0版本更新
2020/04/09 星际争霸
将OICQ数据转成MYSQL数据
2006/10/09 PHP
最令PHP初学者们头痛的十四个问题
2007/01/15 PHP
php生成缩略图示例代码分享(使用gd库实现)
2014/01/20 PHP
ThinkPHP使用心得分享-上传类UploadFile的使用
2014/05/15 PHP
PHP加密3DES报错 Call to undefined function: mcrypt_module_open() 如何解决
2016/04/17 PHP
PHP常量define和const的区别详解
2019/05/18 PHP
laravel 框架执行流程与原理简单分析
2020/02/01 PHP
用JavaScript调用WebService的示例
2008/04/07 Javascript
jquery入门—数据删除与隔行变色以及图片预览
2013/01/07 Javascript
JS图片根据鼠标滚动延时加载的实例代码
2013/07/13 Javascript
javascript中如何处理引号编码"
2013/08/15 Javascript
js判断url是否有效的两种方法
2014/03/04 Javascript
javascript递归回溯法解八皇后问题
2015/04/22 Javascript
javascript实现设置、获取和删除Cookie的方法
2015/06/01 Javascript
JavaScript Split()方法
2015/12/18 Javascript
原生JavaScript编写canvas版的连连看游戏
2016/05/29 Javascript
JavaScript 中 avalon绑定属性总结
2016/10/19 Javascript
值得分享的JavaScript实现图片轮播组件
2016/11/21 Javascript
详解如何将angular-ui的图片轮播组件封装成一个指令
2017/05/09 Javascript
用最简单的方法判断JavaScript中this的指向(推荐)
2017/09/04 Javascript
angularjs http与后台交互的实现示例
2018/12/21 Javascript
Three.js实现简单3D房间布局
2018/12/30 Javascript
js实现三角形粒子运动
2020/09/22 Javascript
浅析Python中return和finally共同挖的坑
2017/08/18 Python
Python使用Selenium+BeautifulSoup爬取淘宝搜索页
2018/02/24 Python
python自动点赞功能的实现思路
2020/02/26 Python
keras CNN卷积核可视化,热度图教程
2020/06/22 Python
斯图尔特·韦茨曼鞋加拿大官网:Stuart Weitzman加拿大
2019/10/13 全球购物
西安启天科技有限公司网络工程师面试题笔试题
2016/06/12 面试题
2014年党员承诺书范文
2014/05/20 职场文书
全运会口号
2014/06/20 职场文书
忠诚教育心得体会
2014/09/03 职场文书
2015年社区服务活动总结
2015/03/25 职场文书
2015年文员个人工作总结
2015/04/09 职场文书
SpringBoot深入分析讲解监听器模式下
2022/07/15 Java/Android