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中的多线程实例教程
Aug 27 Python
Python selenium 父子、兄弟、相邻节点定位方式详解
Sep 15 Python
python中OrderedDict的使用方法详解
May 05 Python
python 简单的绘图工具turtle使用详解
Jun 21 Python
深入理解python中函数传递参数是值传递还是引用传递
Nov 07 Python
基于Django的ModelForm组件(详解)
Dec 07 Python
python实践项目之监控当前联网状态详情
May 23 Python
PyQt5 多窗口连接实例
Jun 19 Python
wxPython绘图模块wxPyPlot实现数据可视化
Nov 19 Python
Python编程快速上手——Excel表格创建乘法表案例分析
Feb 28 Python
Python稀疏矩阵及参数保存代码实现
Apr 18 Python
python 制作网站筛选工具(附源码)
Jan 21 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
phpMyAdmin出现无法载入 mcrypt 扩展,请检查PHP配置的解决方法
2012/03/26 PHP
PHP合并两个数组的两种方式的异同
2012/09/14 PHP
php获取网页请求状态程序示例
2014/06/17 PHP
php调用KyotoTycoon简单实例
2015/04/02 PHP
PHP实现的memcache环形队列类实例
2015/07/28 PHP
php 使用html5实现多文件上传实例
2016/10/24 PHP
php实现姓名根据首字母排序的类与方法(实例代码)
2018/05/16 PHP
JS 自动安装exe程序
2008/11/30 Javascript
Mootools 1.2教程(3) 数组使用简介
2009/09/14 Javascript
jQuery判断div随滚动条滚动到一定位置后停止
2014/04/02 Javascript
javascript实现checkbox全选的代码
2015/04/30 Javascript
JS如何实现文本框随文本的长度而增长
2015/07/30 Javascript
Javascript随机标签云代码实例
2016/06/21 Javascript
基于JS实现类似支付宝支付密码输入框
2016/09/02 Javascript
微信小程序 MINA文件结构
2016/10/17 Javascript
使用vue.js2.0 + ElementUI开发后台管理系统详细教程(一)
2017/01/21 Javascript
简单实现js进度条加载效果
2020/03/25 Javascript
JS实现带导航城市列表以及输入搜索功能
2018/01/04 Javascript
基于VUE移动音乐WEBAPP跨域请求失败的解决方法
2018/01/16 Javascript
p5.js临摹旋转爱心
2019/10/23 Javascript
在vue-cli中引入lodash.js并使用详解
2019/11/13 Javascript
js实现无刷新监听URL的变化示例代码详解
2020/06/03 Javascript
vue 图片裁剪上传组件的实现
2020/11/12 Javascript
Python strip lstrip rstrip使用方法
2008/09/06 Python
Python MySQLdb模块连接操作mysql数据库实例
2015/04/08 Python
利用Python2下载单张图片与爬取网页图片实例代码
2017/12/25 Python
Python实现的爬取小说爬虫功能示例
2019/03/30 Python
Trip.com香港网站:Ctrip携程旗下,全球最大的网上旅游社之一
2016/08/01 全球购物
应届生文秘专业个人自荐信格式
2013/09/21 职场文书
蛋糕店的商业计划书范文
2014/01/27 职场文书
开学季活动策划方案
2014/02/28 职场文书
卫生院健康教育实施方案
2014/06/07 职场文书
教师党的群众路线教育实践活动个人整改方案
2014/10/31 职场文书
离职感谢信
2015/01/21 职场文书
python爬取企查查企业信息之selenium自动模拟登录企查查
2021/04/08 Python
idea下配置tomcat避坑详解
2022/04/12 Servers