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简单获取自身外网IP的方法
Sep 18 Python
Python实现自动登录百度空间的方法
Jun 10 Python
python函数中return后的语句一定不会执行吗?
Jul 06 Python
Python实现将照片变成卡通图片的方法【基于opencv】
Jan 17 Python
Python利用matplotlib.pyplot绘图时如何设置坐标轴刻度
Apr 09 Python
python调用API实现智能回复机器人
Apr 10 Python
Python利用splinter实现浏览器自动化操作方法
May 11 Python
Pycharm运行加载文本出现错误的解决方法
Jun 27 Python
python 求10个数的平均数实例
Dec 16 Python
Python3与fastdfs分布式文件系统如何实现交互
Jun 23 Python
pytorch 中autograd.grad()函数的用法说明
May 12 Python
Python 读写 Matlab Mat 格式数据的操作
May 19 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
深入了解php4(1)--回到未来
2006/10/09 PHP
将数组写入txt文件 var_export
2009/04/21 PHP
PHP5中使用PDO连接数据库的方法
2010/08/01 PHP
php中判断数组是一维,二维,还是多维的解决方法
2013/05/04 PHP
PHP函数实现从一个文本字符串中提取关键字的方法
2015/07/01 PHP
PHP网站建设的流程与步骤分享
2015/09/25 PHP
mac系统下安装多个php并自由切换的方法详解
2017/04/21 PHP
JavaScript-世界上误解最深的语言分析
2007/08/12 Javascript
基于jQuery的Spin Button自定义文本框数值自增或自减
2010/07/17 Javascript
关于js数组去重的问题小结
2014/01/24 Javascript
javascript运行机制之this详细介绍
2014/02/07 Javascript
javascript与有限状态机详解
2014/05/08 Javascript
关于javaScript注册click事件传递参数的不成功问题
2014/07/18 Javascript
封装好的js判断操作系统与浏览器代码分享
2015/01/09 Javascript
Javascript中replace()小结
2015/09/30 Javascript
基于BootStrap与jQuery.validate实现表单提交校验功能
2016/12/22 Javascript
javascript表达式和运算符详解
2017/02/07 Javascript
详解Sea.js中Module.exports和exports的区别
2017/02/12 Javascript
Vue.js 使用v-cloak后仍显示变量的解决方法
2018/11/19 Javascript
Python实现全角半角转换的方法
2014/08/18 Python
tensorflow 中对数组元素的操作方法
2018/07/27 Python
python面试题小结附答案实例代码
2019/04/11 Python
flask框架自定义url转换器操作详解
2020/01/25 Python
python保留格式汇总各部门excel内容的实现思路
2020/06/01 Python
python MD5加密的示例
2020/10/19 Python
玩具反斗城天猫官方旗舰店:享誉全球的玩具店
2017/10/10 全球购物
爱尔兰电子产品购物网站:Komplett.ie
2018/04/04 全球购物
世界顶级户外运动品牌折扣网站:LeftLane Sports
2019/06/12 全球购物
美国踏板车和轻便摩托车销售网站:Mega Motor Madness
2020/02/26 全球购物
EJB包括(SessionBean,EntityBean)说出他们的生命周期,及如何管理事务的
2015/07/24 面试题
后勤服务中心总经理工作职责
2014/03/03 职场文书
工作犯错保证书
2015/05/11 职场文书
2016年秋季运动会通讯稿
2015/11/25 职场文书
Vue3 Composition API的使用简介
2021/03/29 Vue.js
Python实现简繁体转换
2021/06/07 Python
浅谈Redis中的RDB快照
2021/06/29 Redis