关闭

[gevent源码分析] 深度分析gevent运行流程

23298人阅读 评论(6) 收藏 举报
分类:

一直对gevent运行流程比较模糊,最近看源码略有所得,不敢独享,故分享之。

gevent是一个高性能网络库,底层是libevent,1.0版本之后是libev,核心是greenlet。gevent和eventlet是亲近,唯一不同的是eventlet是自己实现的事件驱动,而gevent是使用libev。两者都有广泛的应用,如openstack底层网络通信使用eventlet,goagent是使用gevent。


要想理解gevent首先要理解gevent的调度流程,gevent中有一个hub的概念,也就是下图的MainThread,用于调度所有其它的greenlet实例(下图Coroutine)。
其实hub也是一个greenlet,只不过特殊一些。
看下图我们会发现每次从hub切换到一个greenlet后,都会回到hub,这就是gevent的关键。
注意:gevent中并没有greenlet链的说法,所有都是向主循环注册greenlet.switch方法,主循环在合适的时机切换回来。

也许大家会好奇,为什么采用这种模式,为什么每次都要切换到hub?我想理由有二:

1.hub是事件驱动的核心,每次切换到hub后将继续循环事件。如果在一个greenlet中不出来,那么其它greenlet将得不到调用。

2.维持两者关系肯定比维持多个关系简单。每次我们所关心的就是hub以及当前greenlet,不需要考虑各个greenlet之间关系。


我们看看最简单的gevent.sleep发生了什么?

我们先想想最简单的sleep(0)该如何调度?根据上面很明显

1.向事件循环注册当前greenlet的switch函数

2.切换到hub,运行主事件循环

def sleep(seconds=0, ref=True):
    hub = get_hub()
    loop = hub.loop
    if seconds <= 0:
        waiter = Waiter()
        loop.run_callback(waiter.switch)
        waiter.get()
    else:
        hub.wait(loop.timer(seconds, ref=ref))
当seconds小于等于0时,loop.run_callback(waiter.switch)即是将当前greenlet的switch注册到loop,使用waiter.get()切换到hub。那么很明显,

当切换到hub后当调用刚注册的回调(waiter.switch)回到刚刚sleep所在的greenlet。

不熟悉Waiter的童鞋可能对上面说的有点模糊,下面我们好好看看Waiter是什么。

>>> result = Waiter()
>>> timer = get_hub().loop.timer(0.1)
>>> timer.start(result.switch, 'hello from Waiter')
>>> result.get() # blocks for 0.1 seconds
    'hello from Waiter'
timer.start(result.switch, 'hello from Waiter')我们向hub的主循环注册一个0.1s的定时器,回调为result.switch,然后将执行result.get(),此时过程代码如下:

def get(self):
            assert self.greenlet is None, 'This Waiter is already used by %r' % (self.greenlet, )
            self.greenlet = getcurrent()
            try:
                return self.hub.switch()
            finally:
                self.greenlet = None

将把self.greenlet设置为当前greenlet,然后通过self.hub.switch()切换到主循环,很明显在主循环中将回调result.switch,看代码:

def switch(self, value=None):
            """Switch to the greenlet if one's available. Otherwise store the value."""
            greenlet = self.greenlet
            assert getcurrent() is self.hub, "Can only use Waiter.switch method from the Hub greenlet"
            switch = greenlet.switch
            try:
                switch(value)
            except:
                self.hub.handle_error(switch, *sys.exc_info())
拿到刚保存的greenlet,然后切换到greenlet.switch(),返回到我们刚调用reuslt.get()方法。通过上面assert我们也可以看出这是在hub中调用的。

通过以上分析,小伙伴们肯定都懂了gevent的执行流程了。

这里有个问题,如果上面先发生result.switch,那又该如何呢?就像下面这样:

>>> result = Waiter()
>>> timer = get_hub().loop.timer(0.1)
>>> timer.start(result.switch, 'hi from Waiter')
>>> sleep(0.2)
>>> result.get() # returns immediatelly without blocking
    'hi from Waiter'
我想聪明的你,打开hub.py再看看源码肯定就明白了(上面Waiter代码是我特意简化的)。

既然我们知道了gevent运行流程,下面我们看看gevent.spawn和join到底做了什么?

gevent.spawn其实就是Greenlet.spawn,所以gevent.spawn就是创建一个greenlet,并将该greenlet的switch()加入hub主循环回调。

class Greenlet(greenlet):
    """A light-weight cooperatively-scheduled execution unit."""

    def __init__(self, run=None, *args, **kwargs):
        hub = get_hub()
        greenlet.__init__(self, parent=hub)
        if run is not None:
            self._run = run
        self._start_event = None

    def start(self):
        """Schedule the greenlet to run in this loop iteration"""
        if self._start_event is None:
            self._start_event = self.parent.loop.run_callback(self.switch)

    @classmethod
    def spawn(cls, *args, **kwargs):
        """Return a new :class:`Greenlet` object, scheduled to start.

        The arguments are passed to :meth:`Greenlet.__init__`.
        """
        g = cls(*args, **kwargs)
        g.start()
        return g

通过下面代码证明:

import gevent

def talk(msg):
    print(msg)

g1 = gevent.spawn(talk, 'bar')
gevent.sleep(0)

将输出:bar,我们通过sleep切换到hub,然后hub将运行我们添加的回调talk,一切正常。

此时不要沾沾自喜,如果下面代码也觉得一切正常再高兴也不迟。

import gevent

def talk(msg):
    print(msg)
    gevent.sleep(0)
    print msg

g1 = gevent.spawn(talk, 'bar')
gevent.sleep(0)

这次还是输出:bar,有点不对劲啊,应该输出两个bar才对,为什么为导致这样呢?

我们来好好分析流程:

1.gevent.spawn注册回调talk

2.然后最后一行gevent.sleep(0)注册当前greenlet.switch(最外面的)到hub,然后切换到hub

3.hub执行回调talk,打印"bar",此时gevent.sleep再次将g1.switch注册到hub,同时切换到hub

4.由于第2步最外层greenlet现注册,所以将调用最外层greenlet,此时很明显,程序将结束。因为最外层greenlet并不是hub的子greenlet,

所以died后并不会回到父greenlet,即hub


你可能会说那我自己手动切换到hub不就可以了吗?这将导致主循环结束不了的问题。

import gevent

def talk(msg):
    print(msg)
    gevent.sleep(0)
    print msg

g1 = gevent.spawn(talk, 'bar')
gevent.get_hub().switch()
程序输出:

bar
bar
Traceback (most recent call last):
  File "F:\py_cgi\geve.py", line 9, in <module>
    gevent.get_hub().switch()
  File "C:\Python26\lib\site-packages\gevent\hub.py", line 331, in switch
    return greenlet.switch(self)
gevent.hub.LoopExit: This operation would block forever
虽然成功的输出了两次“bar",但也导致了更为严重的问题。

这也就是join存在的价值,我们看看join是如何做到的?

    def join(self, timeout=None):
        """Wait until the greenlet finishes or *timeout* expires.
        Return ``None`` regardless.
        """
        if self.ready():
            return
        else:
            switch = getcurrent().switch
            self.rawlink(switch)
            try:
                t = Timeout.start_new(timeout)
                try:
                    result = self.parent.switch()
                    assert result is self, 'Invalid switch into Greenlet.join(): %r' % (result, )
                finally:
                    t.cancel()
            except Timeout:
                self.unlink(switch)
                if sys.exc_info()[1] is not t:
                    raise
            except:
                self.unlink(switch)
                raise


    def rawlink(self, callback):
        """Register a callable to be executed when the greenlet finishes the execution.

        WARNING: the callable will be called in the HUB greenlet.
        """
        if not callable(callback):
            raise TypeError('Expected callable: %r' % (callback, ))
        self._links.append(callback)
        if self.ready() and self._links and not self._notifier:
            self._notifier = self.parent.loop.run_callback(self._notify_links)

    def _notify_links(self):
        while self._links:
            link = self._links.popleft()
            try:
                link(self)
            except:
                self.parent.handle_error((link, self), *sys.exc_info())

从代码中可以看出,join会保存当前greenlet.switch到一个队列中,并注册_notify_links回调,然后切换到hub,在_notify_links回调中将依次调用先前注册在队列中的回调。

而我们调用g1.join()将会把最外层greenlet.switch注册到队列中,当回调时就顺利结束程序了。很完美!!!



10
3
查看评论

python gevent多线程练习

最近上课时老师说,你们现在写的程序全是垃圾,你们现在必须建立起来一个多线程的概念,只有在多线程或者多进程下你们的程序才能看起来像样子.这两天偶然看到gevent可以来实现多线程,于是做一些小的练习!       首先下载gevent模块: pip...
  • wangyu190810
  • wangyu190810
  • 2013-10-06 10:27
  • 7088

gevent 小结

最近用了 gevent,做个小结,理理对 gevent 的认识。 什么是 gevent? 简介 (摘自 官网) gevent is a coroutine-based Python networking library that use...
  • LaoLiulaoliu
  • LaoLiulaoliu
  • 2015-01-04 22:44
  • 2088

Gevent源码之loop的实现

gevent之所以性能好,最主要就得益于对libev的封装,这里就来看看这部分具体的实现。。。 稍微看一下libev的用法就知道,libev将各种事件都定义为了watcher,这里包括了定时,io等等。。 在gevent主要就是对libev的loop以及watcher进行了封装。。这部分采用的是cy...
  • fjs_cloud
  • fjs_cloud
  • 2014-09-09 15:27
  • 1891

[gevent源码分析] gevent两架马车-libev和greenlet

本篇将讨论gevent的两架马车-libev和greenlet如何协同工作的。 gevent事件驱动底层使用了libev,我们先看看如何单独使用gevent中的事件循环。 [python] view plain copy   ...
  • shuyun123456789
  • shuyun123456789
  • 2016-12-10 12:05
  • 348

Gevent的协程实现原理

之前之所以看greenlet的代码实现,主要就是想要看看gevent库的实现代码。。。然后知道了gevent的协程是基于greenlet来实现的。。。所以就又先去看了看greenlet的实现。。。 这里就不说greenlet的具体实现了,关键就是栈数据的复制拷贝,栈指针的位移。。。 因为geven...
  • fjs_cloud
  • fjs_cloud
  • 2014-09-02 18:29
  • 4629

[gevent源码分析] gevent两架马车-libev和greenlet

本篇将讨论gevent的两架马车-libev和greenlet如何协同工作的。gevent事件驱动底层使用了libev,我们先看看如何单独使用gevent中的事件循环。#coding=utf8 import socket import gevent from gevent.core import l...
  • yueguanghaidao
  • yueguanghaidao
  • 2014-09-08 00:02
  • 9649

Gevent的协程实现原理

之前之所以看greenlet的代码实现,主要就是想要看看gevent库的实现代码。。。然后知道了gevent的协程是基于greenlet来实现的。。。所以就又先去看了看greenlet的实现。。。 这里就不说greenlet的具体实现了,关键就是栈数据的复制拷贝,栈指针的位移。。。因为gevent...
  • qugemingzhennimanan
  • qugemingzhennimanan
  • 2017-02-25 20:15
  • 153

python基于协程的网络库gevent、eventlet

python网络库也有了基于协程的实现,比较著名的是 gevent、eventlet 它两之间的关系可以参照 Comparing gevent to eventlet, 本文主要简单介绍一下eventlet一个例子 #! /usr/bin/env python ""&quo...
  • mumumuwudi
  • mumumuwudi
  • 2015-07-29 00:44
  • 3049

eventlet引发的学习:协程

说明本文与编程语言无关,只是介绍协程的一些概念,协程能解决的问题等。只是文章的最后列出了一些协程在python中的实现。概述wikipedia:Coroutines中对Coroutines进行了如下对解释。 Coroutines are computer program components t...
  • K_Zombie
  • K_Zombie
  • 2016-03-09 23:41
  • 673

Gevent简介

介绍 gevent 是一个python网络框架,对应Java的netty框架,使用greenlet提供异步API,基于libevent ,它为各种并发和网络相关的任务提供了整洁的API. 快速基于libevent的event loop基于greenlet的轻量级执行单元重用python标...
  • permike
  • permike
  • 2016-08-29 16:18
  • 740
    个人资料
    • 访问:1288693次
    • 积分:10410
    • 等级:
    • 排名:第1938名
    • 原创:121篇
    • 转载:11篇
    • 译文:3篇
    • 评论:356条
    博客专栏
    最新评论