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使用socket连接远程服务器的方法
Apr 29 Python
总结Python编程中三条常用的技巧
May 11 Python
Python的Django框架中自定义模版标签的示例
Jul 20 Python
Python调用C# Com dll组件实战教程
Oct 12 Python
Python迭代器与生成器基本用法分析
Jul 26 Python
Python生成器generator用法示例
Aug 10 Python
浅析matlab中imadjust函数
Feb 27 Python
踩坑:pytorch中eval模式下结果远差于train模式介绍
Jun 23 Python
使用keras实现BiLSTM+CNN+CRF文字标记NER
Jun 29 Python
Python识别验证码的实现示例
Sep 30 Python
python Autopep8实现按PEP8风格自动排版Python代码
Mar 02 Python
Python+腾讯云服务器实现每日自动健康打卡
Dec 06 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判断表是否存在的方法
2015/06/18 PHP
php登录超时检测功能实例详解
2017/03/21 PHP
对 jQuery 中 data 方法的误解分析
2014/06/18 Javascript
Javascript 学习笔记之 对象篇(二) : 原型对象
2014/06/24 Javascript
jquery ajax局部加载方法详解(实现代码)
2016/05/12 Javascript
JS正则表达式完美实现身份证校验功能
2017/10/18 Javascript
详解vue-cli快速构建vue应用并实现webpack打包
2017/12/13 Javascript
JS兼容所有浏览器的DOMContentLoaded事件
2018/01/12 Javascript
详解IOS微信上Vue单页面应用JSSDK签名失败解决方案
2018/11/14 Javascript
小程序中canvas的drawImage方法参数使用详解
2019/07/04 Javascript
微信小程序 WXML节点信息查询详解
2019/07/29 Javascript
JS中getElementsByClassName与classList兼容性问题解决方案分析
2019/08/07 Javascript
微信小程序页面间传递数组对象方法解析
2019/11/06 Javascript
微信小程序实现签到弹窗动画
2020/09/21 Javascript
微信小程序实现列表左右滑动
2020/11/19 Javascript
python函数返回多个值的示例方法
2013/12/04 Python
使用graphics.py实现2048小游戏
2015/03/10 Python
Python命令行参数解析模块optparse使用实例
2015/04/13 Python
python Django批量导入不重复数据
2016/03/25 Python
python matplotlib 注释文本箭头简单代码示例
2018/01/08 Python
PyQt 实现使窗口中的元素跟随窗口大小的变化而变化
2019/06/18 Python
PyTorch中Tensor的拼接与拆分的实现
2019/08/18 Python
python如何保证输入键入数字的方法
2019/08/23 Python
Python要求O(n)复杂度求无序列表中第K的大元素实例
2020/04/02 Python
python查看矩阵的行列号以及维数方式
2020/05/22 Python
Pycharm plot独立窗口显示的操作
2020/12/11 Python
俄罗斯街头服装品牌:Black Star Wear
2017/03/01 全球购物
女士鞋子、包包和服装在线,第一款10美元:ShoeDazzle
2019/07/26 全球购物
T3官网:头发造型工具
2019/12/26 全球购物
自然健康的概念:Natural Healthy Concepts
2020/01/26 全球购物
short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
2014/09/26 面试题
经典英文广告词
2014/03/18 职场文书
信息管理与信息系统专业求职信
2014/06/21 职场文书
驳回起诉民事裁定书
2015/05/19 职场文书
一篇文章弄懂Python中的内建函数
2021/08/07 Python
MySQL池化框架学习接池自定义
2022/07/23 MySQL