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 相关文章推荐
详谈在flask中使用jsonify和json.dumps的区别
Mar 26 Python
python微信公众号之关注公众号自动回复
Oct 25 Python
python ChainMap的使用和说明详解
Jun 11 Python
python 函数中的内置函数及用法详解
Jul 02 Python
安装2019Pycharm最新版本的教程详解
Oct 22 Python
Pandas 缺失数据处理的实现
Nov 04 Python
使用python实现哈希表、字典、集合操作
Dec 22 Python
Python有参函数使用代码实例
Jan 06 Python
Python3爬虫里关于识别微博宫格验证码的知识点详解
Jul 30 Python
Python 操作SQLite数据库的示例
Oct 16 Python
scrapy redis配置文件setting参数详解
Nov 18 Python
Python+MySQL随机试卷及答案生成程序的示例代码
Feb 01 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网页游戏学习之Xnova(ogame)源码解读(九)
2014/06/24 PHP
总结PHP中数值计算的注意事项
2016/08/14 PHP
linux平台编译安装PHP7并安装Redis扩展与Swoole扩展实例教程
2016/09/30 PHP
php 截取GBK文档某个位置开始的n个字符方法
2017/03/08 PHP
PHP 二维关联数组根据其中一个字段排序(推荐)
2017/04/04 PHP
Js 本页面传值实现代码
2009/05/17 Javascript
jQuery下实现等待指定元素加载完毕(可改成纯js版)
2013/07/11 Javascript
整理的比较全的event对像在ie与firefox浏览器中的区别
2013/11/25 Javascript
初识SmartJS - AOP三剑客
2014/06/08 Javascript
jquery实现显示已选用户
2014/07/21 Javascript
javascript实现切换td中的值
2014/12/05 Javascript
HTML5之WebSocket入门3 -通信模型socket.io
2015/08/21 Javascript
详细分析Javascript中创建对象的四种方式
2016/08/17 Javascript
Bootstrapvalidator校验、校验清除重置的实现代码(推荐)
2016/09/28 Javascript
Vuex模块化实现待办事项的状态管理
2017/03/15 Javascript
PHP实现记录代码运行时间封装类实例教程
2017/05/08 Javascript
vue中将网页打印成pdf实例代码
2017/06/15 Javascript
微信小程序学习之数据处理详解
2017/07/05 Javascript
JavaScript中错误正确处理方式小结你用对了吗
2017/10/10 Javascript
iview日期控件,双向绑定日期格式的方法
2018/03/15 Javascript
深入浅析Node环境和浏览器的区别
2018/08/14 Javascript
Python中的Matplotlib模块入门教程
2015/04/15 Python
Python读取网页内容的方法
2015/07/30 Python
Python处理CSV与List的转换方法
2018/04/19 Python
Python中psutil的介绍与用法
2019/05/02 Python
Django模板导入母版继承和自定义返回Html片段过程解析
2019/09/18 Python
python pygame实现滚动横版射击游戏城市之战
2019/11/25 Python
浅谈python中频繁的print到底能浪费多长时间
2020/02/21 Python
Python实现ElGamal加密算法的示例代码
2020/06/19 Python
意大利高端时尚买手店:Stefania Mode
2018/03/01 全球购物
基于Python 函数和方法的区别说明
2021/03/24 Python
企业后勤岗位职责
2014/02/28 职场文书
餐饮总经理岗位职责
2014/03/07 职场文书
竞选部长演讲稿
2014/04/26 职场文书
2019年冬至:天冷暖人心的问候祝福语大全
2019/12/20 职场文书
html实现随机点名器的示例代码
2021/04/02 Javascript