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中无限元素列表的实现方法
Aug 18 Python
django实现分页的方法
May 26 Python
Python的CGIHTTPServer交互实现详解
Feb 08 Python
Python利用pandas计算多个CSV文件数据值的实例
Apr 19 Python
Python实现的读写json文件功能示例
Jun 05 Python
Python安装pycurl失败的解决方法
Oct 15 Python
Python3转换html到pdf的不同解决方案
Mar 11 Python
opencv之为图像添加边界的方法示例
Dec 26 Python
导入tensorflow:ImportError: libcublas.so.9.0 报错
Jan 06 Python
python 爬取哔哩哔哩up主信息和投稿视频
Jun 07 Python
python之PySide2安装使用及QT Designer UI设计案例教程
Jul 26 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获得用户ip地址的比较不错的方法
2014/02/08 PHP
Linux php 中文乱码的快速解决方法
2016/05/13 PHP
PHP迭代器和生成器用法实例分析
2019/09/28 PHP
解决Laravel 不能创建 migration 的问题
2019/10/09 PHP
php 多继承的几种常见实现方法示例
2019/11/18 PHP
实现超用户体验 table排序javascript实现代码
2009/06/22 Javascript
理解 JavaScript 预解析
2009/10/25 Javascript
js动态创建上传表单通过iframe模拟Ajax实现无刷新
2014/02/20 Javascript
javascript的函数作用域
2014/11/12 Javascript
JavaScript字符串常用的方法
2016/03/10 Javascript
Google 地图控件集详解及实例代码
2016/08/06 Javascript
jQuery页面弹出框实现文件上传
2017/02/09 Javascript
微信小程序开发animation心跳动画效果
2017/08/16 Javascript
AngularJS下$http服务Post方法传递json参数的实例
2018/03/29 Javascript
Vue起步(无cli)的啊教程详解
2019/04/11 Javascript
Vue注册组件命名时不能用大写的原因浅析
2019/04/25 Javascript
生成无限制的微信小程序码的示例代码
2019/09/20 Javascript
nodemon实现Typescript项目热更新的示例代码
2019/11/19 Javascript
vant-ui组件调用Dialog弹窗异步关闭操作
2020/11/04 Javascript
[01:24:34]2014 DOTA2华西杯精英邀请赛5 24 DK VS LGD
2014/05/25 DOTA
[01:20]辉夜杯背景故事宣传片《辉夜传说》
2015/12/25 DOTA
python使用super()出现错误解决办法
2017/08/14 Python
Windows下安装Django框架的方法简明教程
2018/03/28 Python
关于pycharm中pip版本10.0无法使用的解决办法
2019/10/10 Python
简单了解为什么python函数后有多个括号
2019/12/19 Python
python使用numpy中的size()函数实例用法详解
2021/01/29 Python
安全教育心得体会
2013/12/29 职场文书
初中英语演讲稿
2014/04/29 职场文书
知识就是力量演讲稿
2014/09/13 职场文书
四风问题自我剖析材料
2014/10/07 职场文书
2014年业务员工作总结范文
2014/11/17 职场文书
长城导游词300字
2015/01/30 职场文书
自我工作评价范文
2015/03/06 职场文书
餐厅保洁员岗位职责
2015/04/10 职场文书
船舶调度指挥系统——助力智慧海事
2022/02/18 无线电
python高温预警数据获取实例
2022/07/23 Python