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和shell变量互相传递的几种方法
Nov 20 Python
Python的Django中django-userena组件的简单使用教程
May 30 Python
python 将字符串转换成字典dict的各种方式总结
Mar 23 Python
Python关于excel和shp的使用在matplotlib
Jan 03 Python
python按照多个条件排序的方法
Feb 08 Python
python创建ArcGIS shape文件的实现
Dec 06 Python
django框架两个使用模板实例
Dec 11 Python
Scrapy框架基本命令与settings.py设置
Feb 06 Python
Django添加bootstrap框架时无法加载静态文件的解决方式
Mar 27 Python
python求前n个阶乘的和实例
Apr 02 Python
分享一个python的aes加密代码
Dec 22 Python
matplotlib 使用 plt.savefig() 输出图片去除旁边的空白区域
Jan 05 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与MySQL开发的8个技巧小结
2010/12/17 PHP
linux下使用ThinkPHP需要注意大小写导致的问题
2011/08/02 PHP
PHP Global定义全局变量使用说明
2013/08/15 PHP
php setcookie函数的参数说明及其用法
2014/04/20 PHP
PHP列出MySQL中所有数据库的方法
2015/03/12 PHP
利用PHP_XLSXWriter代替PHPExcel的方法示例
2017/07/16 PHP
PHP架构及原理知识点详解
2019/12/22 PHP
一个不错的应用,用于提交获取文章内容,不推荐用
2007/03/03 Javascript
JavaScript在IE和Firefox浏览器下的7个差异兼容写法小结
2010/06/18 Javascript
javascript中的关于类型转换的性能优化
2010/12/14 Javascript
js读写(删除)Cookie实例详解
2013/04/17 Javascript
javascript函数作用域学习示例(js作用域)
2014/01/13 Javascript
javascript 获取元素样式必杀技
2014/05/04 Javascript
js实现适用于素材网站的黑色多级菜单导航条效果
2015/08/24 Javascript
js判断移动端是否安装某款app的多种方法
2015/12/18 Javascript
基于JavaScript实现全屏透明遮罩div层锁屏效果
2016/01/26 Javascript
浅谈Cookie的生命周期问题
2016/08/02 Javascript
js实现select选择框效果及美化
2016/08/19 Javascript
jquery实现全选、不选、反选的两种方法
2016/09/06 Javascript
echarts3 使用总结(绘制各种图表,地图)
2017/01/05 Javascript
C#微信小程序服务端获取用户解密信息实例代码
2017/03/10 Javascript
Bootstrap输入框组件使用详解
2017/06/09 Javascript
深入理解Vue2.x的虚拟DOM diff原理
2017/09/27 Javascript
Vue中computed、methods与watch的区别总结
2019/04/10 Javascript
vue的keep-alive中使用EventBus的方法
2019/04/23 Javascript
三分钟教你用Node做一个微信哄女友(基友)神器(面向小白)
2019/06/21 Javascript
vue监听用户输入和点击功能
2019/09/27 Javascript
[01:18:36]LGD vs VP Supermajor 败者组决赛 BO3 第一场 6.10
2018/07/04 DOTA
Python中常用的8种字符串操作方法
2019/05/06 Python
python logging添加filter教程
2019/12/24 Python
初中生物教学反思
2014/01/10 职场文书
计划生育宣传标语
2014/06/21 职场文书
代理人委托书
2014/08/01 职场文书
班子四风对照检查材料
2014/08/21 职场文书
vue里使用create, mounted调用方法
2022/04/26 Vue.js