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开发WebService系列教程之REST,web.py,eurasia,Django
Jun 30 Python
Python多线程下载文件的方法
Jul 10 Python
python实现对csv文件的列的内容读取
Jul 04 Python
Flask之请求钩子的实现
Dec 23 Python
matlab灰度图像调整及imadjust函数的用法详解
Feb 27 Python
python使用gdal对shp读取,新建和更新的实例
Mar 10 Python
python pandas利用fillna方法实现部分自动填充功能
Mar 16 Python
Python函数的迭代器与生成器的示例代码
Jun 18 Python
完美解决TensorFlow和Keras大数据量内存溢出的问题
Jul 03 Python
python PIL模块的基本使用
Sep 29 Python
pycharm无法导入lxml的解决办法
Mar 31 Python
Python开发之QT解决无边框界面拖动卡屏问题(附带源码)
May 27 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的sprintf函数的用法 控制浮点数格式
2014/02/14 PHP
PHP网页安全认证的实例详解
2017/09/28 PHP
Laravel 实现密码重置功能
2018/02/23 PHP
PHP程序员简单的开展服务治理架构操作详解(二)
2020/05/14 PHP
JavaScript更改class和id的方法
2008/10/10 Javascript
jQuery中jqGrid分页实现代码
2011/11/04 Javascript
jQuery 快速结束当前正在执行的动画
2013/11/20 Javascript
Javascript中call的两种用法实例
2013/12/13 Javascript
jquery等待效果示例
2014/05/01 Javascript
Extjs grid panel自带滚动条失效的解决方法
2014/09/11 Javascript
JavaScript基本语法学习教程
2016/01/14 Javascript
jQuery如何获取动态添加的元素
2016/06/24 Javascript
bootstrap fileinput组件整合Springmvc上传图片到本地磁盘
2017/05/11 Javascript
Vue 2.0的数据依赖实现原理代码简析
2017/07/10 Javascript
浅谈Vue 初始化性能优化
2017/08/31 Javascript
微信小程序scroll-view横向滑动嵌套for循环的示例代码
2018/09/20 Javascript
express 项目分层实践详解
2018/12/10 Javascript
vue.js 2.0实现简单分页效果
2019/07/29 Javascript
vue实现简易的双向数据绑定
2020/12/29 Vue.js
使用Python3内置文档高效学习以及官方中文文档
2019/05/19 Python
详解用pyecharts Geo实现动态数据热力图城市找不到问题解决
2019/06/26 Python
在cmd中查看python的安装路径方法
2019/07/03 Python
Numpy对数组的操作:创建、变形(升降维等)、计算、取值、复制、分割、合并
2019/08/28 Python
Python列表删除元素del、pop()和remove()的区别小结
2019/09/11 Python
基于Python的图像阈值化分割(迭代法)
2020/11/20 Python
CSS3中的@keyframes关键帧动画的选择器绑定
2016/06/13 HTML / CSS
CSS3实现文本垂直排列的方法
2018/07/10 HTML / CSS
草莓巧克力:Shari’s Berries
2017/02/07 全球购物
TripAdvisor瑞典:全球领先的旅游网站
2017/12/11 全球购物
比利时家具购买网站:Home24
2019/01/03 全球购物
一套PHP的笔试题
2013/05/31 面试题
工作分析计划书
2014/04/30 职场文书
大学生迟到检讨书500字
2014/10/17 职场文书
2015年八一建军节慰问信
2015/03/23 职场文书
让世界充满爱观后感
2015/06/10 职场文书
六一文艺汇演主持词
2015/06/30 职场文书