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 del()函数用法
Mar 24 Python
python发送邮件接收邮件示例分享
Jan 21 Python
python Django框架实现自定义表单提交
Mar 25 Python
python处理Excel xlrd的简单使用
Sep 12 Python
python如何去除字符串中不想要的字符
Jul 05 Python
python使用openpyxl库修改excel表格数据方法
May 03 Python
在python中bool函数的取值方法
Nov 01 Python
用Python获取摄像头并实时控制人脸的实现示例
Jul 11 Python
Python 分享10个PyCharm技巧
Jul 13 Python
pygame实现打字游戏
Feb 19 Python
Python3-异步进程回调函数(callback())介绍
May 02 Python
Python 转移文件至云对象存储的方法
Feb 07 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
用php+javascript实现二级级联菜单的制作
2008/05/06 PHP
php截取字符串之截取utf8或gbk编码的中英文字符串示例
2014/03/12 PHP
PHP+FastCGI+Nginx配置PHP运行环境
2014/08/07 PHP
PHP常用函数总结(180多个)
2016/12/25 PHP
PHP实现随机数字、字母的验证码功能
2018/08/01 PHP
jquery实现可拖动DIV自定义保存到数据的实例
2013/11/20 Javascript
jQuery .tmpl() 用法示例介绍
2014/08/21 Javascript
js实现鼠标感应向下滑动隐藏菜单的方法
2015/02/20 Javascript
JS实现仿苹果底部任务栏菜单效果代码
2015/08/28 Javascript
jQuery定义插件的方法
2015/12/18 Javascript
javascript瀑布流式图片懒加载实例解析与优化
2016/02/23 Javascript
jquery trigger函数执行两次的解决方法
2016/02/29 Javascript
设置jQueryUI DatePicker默认语言为中文
2016/06/04 Javascript
微信小程序 数据访问实例详解
2016/10/08 Javascript
Angular.JS中的指令引用template与指令当做属性详解
2017/03/30 Javascript
微信小程序实现打开内置地图功能【附源码下载】
2017/12/07 Javascript
layui中的switch开关实现方法
2019/09/03 Javascript
KnockoutJS数组比较算法实例详解
2019/11/25 Javascript
JS实现普通轮播图特效
2020/01/01 Javascript
使用element-ui +Vue 解决 table 里包含表单验证的问题
2020/07/17 Javascript
python用reduce和map把字符串转为数字的方法
2016/12/19 Python
通过Pandas读取大文件的实例
2018/06/07 Python
Flask入门之上传文件到服务器的方法示例
2018/07/18 Python
基于python生成器封装的协程类
2019/03/20 Python
Python scipy的二维图像卷积运算与图像模糊处理操作示例
2019/09/06 Python
python 如何将数据写入本地txt文本文件的实现方法
2019/09/11 Python
原生python实现knn分类算法
2019/10/24 Python
django序列化serializers过程解析
2019/12/14 Python
你应该知道的Python3.6、3.7、3.8新特性小结
2020/05/12 Python
Python编写memcached启动脚本代码实例
2020/08/14 Python
python 实现波浪滤镜特效
2020/12/02 Python
Tarte Cosmetics官网:美国最受欢迎的化妆品公司之一
2017/08/24 全球购物
小学语文国培感言
2014/03/04 职场文书
驾驶员培训方案
2014/05/01 职场文书
详解MySQL中的pid与socket
2021/06/15 MySQL
SQL Server中搜索特定的对象
2022/05/25 SQL Server