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 相关文章推荐
PHP魔术方法__ISSET、__UNSET使用实例
Nov 25 Python
Python实现批量读取word中表格信息的方法
Jul 30 Python
Python设置默认编码为utf8的方法
Jul 01 Python
Python基于回溯法子集树模板解决马踏棋盘问题示例
Sep 11 Python
Python 基础教程之闭包的使用方法
Sep 29 Python
Python决策树和随机森林算法实例详解
Jan 30 Python
python学生管理系统代码实现
Apr 05 Python
python中sort和sorted排序的实例方法
Aug 26 Python
python动态视频下载器的实现方法
Sep 16 Python
numpy中生成随机数的几种常用函数(小结)
Aug 18 Python
Pycharm编辑器功能之代码折叠效果的实现代码
Oct 15 Python
解决jupyter notebook图片显示模糊和保存清晰图片的操作
Apr 24 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文件压缩之PHPZip类用法实例
2015/06/18 PHP
Laravel实现自定义错误输出内容的方法
2016/10/10 PHP
php禁用cookie后session设置方法分析
2016/10/19 PHP
利用php做服务器和web前端的界面进行交互
2016/10/31 PHP
thinkPHP框架实现图像裁剪、缩放、加水印的方法
2017/03/14 PHP
PDO操作MySQL的基础教程(推荐)
2017/08/18 PHP
浅谈php://filter的妙用
2019/03/05 PHP
PhpStorm2020.1 安装 debug - Postman 调用的详细教程
2020/08/17 PHP
jquery根据name属性查找的小例子
2013/11/21 Javascript
JavaScript中获取高度和宽度函数总结
2014/10/08 Javascript
JS实现网页上随机产生超链接地址的方法
2015/11/09 Javascript
详解angularJs中自定义directive的数据交互
2017/01/13 Javascript
Vue.js移动端左滑删除组件的实现代码
2017/09/08 Javascript
微信小程序template模板与component组件的区别和使用详解
2019/05/22 Javascript
JavaScript交换变量常用4种方法解析
2020/09/02 Javascript
python对指定目录下文件进行批量重命名的方法
2015/04/18 Python
Python自定义线程类简单示例
2018/03/23 Python
python3+requests接口自动化session操作方法
2018/10/13 Python
Python 给屏幕打印信息加上颜色的实现方法
2019/04/24 Python
Python稀疏矩阵及参数保存代码实现
2020/04/18 Python
python3.6.8 + pycharm + PyQt5 环境搭建的图文教程
2020/06/11 Python
python3实现将json对象存入Redis以及数据的导入导出
2020/07/16 Python
国税会议欢迎词
2014/01/16 职场文书
学习十八大坚定理想信念心得体会
2014/03/11 职场文书
剪彩仪式主持词
2014/03/19 职场文书
国际贸易专业求职信
2014/06/04 职场文书
护理专科学生自荐书
2014/07/05 职场文书
政府个人对照检查材料
2014/08/28 职场文书
报考公务员诚信承诺书
2014/08/29 职场文书
关于拾金不昧的感谢信
2015/01/21 职场文书
2015年建党94周年演讲稿
2015/03/19 职场文书
2015暑期社会实践调查报告
2015/07/14 职场文书
python 如何做一个识别率百分百的OCR
2021/05/29 Python
Javascript 解构赋值详情
2021/11/17 Javascript
JavaScript流程控制(循环)
2021/12/06 Javascript
CSS控制继承中的height能变为可继承吗
2022/06/10 HTML / CSS