Python gevent协程切换实现详解

作者:冷冰若水 时间:2023-01-15 17:50:18 

一、背景

大家都知道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密集型应用吞吐率的目标。

来源:https://www.cnblogs.com/lit10050528/p/13549532.html

标签:Python,gevent,协程
0
投稿

猜你喜欢

  • asp如何设定程序的执行次数?

    2010-05-18 18:31:00
  • Python Requests安装与简单运用

    2023-09-26 22:02:15
  • ajax 同步请求和异步请求的差异分析

    2011-07-05 12:36:04
  • 纯CSS实现导航下拉菜单

    2007-11-25 15:11:00
  • JavaScript 设计模式 富有表现力的Javascript(一)

    2023-08-25 07:42:15
  • 使用:after清除浮动

    2008-10-30 12:10:00
  • Python 计算任意两向量之间的夹角方法

    2022-11-10 07:01:47
  • 小型分页的设计

    2011-08-18 18:32:26
  • Python Pycurl的属性与方法案例详解

    2022-09-27 04:57:23
  • 优化MySQL数据库查询的三种方法

    2009-03-09 15:19:00
  • MySQL修改数据库大小

    2011-01-13 20:00:00
  • python温度转换华氏温度实现代码

    2021-09-01 22:30:49
  • 简单仿LightBox效果

    2008-09-19 21:35:00
  • 使用 Python 实现文件递归遍历的三种方式

    2022-08-19 18:49:08
  • 学点简单的Django之第一个Django程序的实现

    2021-03-23 05:10:59
  • Python实现JSON反序列化类对象的示例

    2023-09-03 19:29:51
  • Javascript将string类型转换int类型

    2023-09-17 00:05:37
  • Python的Web框架Django介绍与安装方法

    2021-03-06 05:46:12
  • 基于ORA-12170 TNS 连接超时解决办法详解

    2023-06-30 18:18:30
  • sqlserver通用的删除服务器上的所有相同后缀的临时表

    2012-06-06 20:07:34
  • asp之家 网络编程 m.aspxhome.com