gevent 学习笔记 —— greenlet

gevent文档:that uses greenlet to provide a high-level synchronous API.

意思是:使用greenlet来提供高级同步API

那greenlet又是怎样一种机制呢?这个后面会稍微简单的讲一下原理,不过在后续的几篇会着重分析它的源码。在开始之前先看看提到的几个关键字『高级』、『同步』,高级不高级暂时还不知道,毕竟在实际生产环境中才看得到效果。而同步这个概念,不知道大家理解深不深刻,总之我当初理解不是很深刻,所以遇到了就聊聊呗!

同步

不明白为什么会叫同步,总是让我有一种错觉,让我联想到的第一个词汇就是同时进行。不知道大家是不是和我一样,至少我看到同步是不会去想到它的正确意思。我曾一度怀疑是不是翻译错了,但是在质疑别人错之前,应该先质疑一下自己。所以我开始怀疑我的语文没学好,然后我去查了一下汉语词典,结果就悲剧了,我已经开始怀疑我的人生了。 %>_<%

同步:指两个或两个以上随时间变化的量在变化过程中保持一定的相对关系。

《光明日报》1984.6.2:“城市改革的步子要加快,要从解决国家与企业、企业与职工的关系入手,把适合于当前情况的各项改革措施初步配起套来,同步进行。

从上面引用的话应该可以看出,国家与企业、企业与职工,都是两个相对的关系,有着各自的利益。但是他们必须合作才能完成一个共同目标,例如买票看电影。这个场景有两个动作需要完成,一个出票、一个买票,只有出票才能买票,并且这个买票的人还只能傻等,不能做其他事。

同步真心不是同时进行,所以还是要多读书啊!尤其是语文!

为什么这里会讲到同步呢?其实不讲也是可以的,和理解greenlet原理没有多大关系,但是和理解gevent的特性有那么点关系,因为听别人说gevent能够让你用这种同步的代码写出异步的感觉,同步的代码好理解也好处理。我没有验证过,所以我也不知道,真正的原因是感觉这个概念很容易混淆,所以就来讲讲了。

Greenlet

还记得在协程篇中学习的吗?花了较长篇幅讲协程及从其子例程演变的过程。而greenlet就是python中实现Coroutine「协程」的一个基础库。前面我们了解了协程的相关概念及思想,但我们还不知道它在底层是怎样实现的。只知道它有个特别的地方,就是能够和进程、线程一样保存上下文,那协程的上下文是怎样保存的呢?

源码初探

typedef struct _greenlet {
    PyObject_HEAD
    char* stack_start;
    char* stack_stop;
    char* stack_copy;
    intptr_t stack_saved;
    struct _greenlet* stack_prev;
    struct _greenlet* parent;
    PyObject* run_info;
    struct _frame* top_frame;
    int recursion_depth;
    PyObject* weakreflist;
    PyObject* exc_type;
    PyObject* exc_value;
    PyObject* exc_traceback;
    PyObject* dict;
} PyGreenlet;

上面给出的是greenlet的结构体定义,有兴趣的可以去下一份源代码看看,里面也有很好的解释。

这里我们主要了解一下关于堆栈的几个操作,从命名规则就可以找到我们想要的东西,就是它们几个了:stack_startstack_stopstack_copystack_saved

其实仔细想想,你会发现非常的简单。假设有『函数A』和『函数B』,『函数A』进栈执行到一半的时候需要调用『函数B』,没事这个简单,我们可以将『函数B』进栈(图stack-01)。

好了『函数B』进栈了,可它现在又想调用『函数A』了,怎么办?让『函数A』进栈(图stack-02),这个其实就是普通的函数调用,此『函数A』并不是第一次进栈的那个『函数A』,而是重新在栈中创建的一个实例,该实例的数据并不是之前『函数A』的数据,可是我想要之前『函数A』的数据啊!所以这个方法并不能实现我们想要的切换。

那怎样才能做到我们想要的呢?很简单,既然『函数B』完成了当前的任务,它就应该退出来了,但并不是直接出栈,而是通过某种方式将现有的state给记录下来,方便下次用到的时候能够找到。怎么记录呢?这里就可以回到上面我们需要了解的几个栈操作了。

首先,每个greenlet都有属于它们自己的stack_startstack_stop,通过这两个可以找到你准备出栈的greenlet也就是『函数B』的所有数据,之后再调用PyMem_Realloc这个方法,就可以在heap中创建一个内存空间用来存放『函数B』,地址为stack_copy,大小为stack_saved字节,通过这两个就可以定位到你存放的『函数B』的数据了(图stack-03)。

之后变成下面(图stack-04)这样了。这个时候,如果『函数A』执行完了想去执行『函数B』的时候,就按照这个流程再保存,然后再把刚才存放的『函数B』给复制到C栈中。因为已经知道了stack_copystack_saved,所以也就不怕找不到它了。

整个greenlet切换流程大概就是这个样子了,不过到了这里不知道大家会不会有些疑问,虽然保存了stack_copystack_saved,但是『函数B』已经出栈了,不是就没有这个对象了吗?那这两个参数是由谁来保存呢?

其实这个对象从来就没有被销毁,只要这个greenlet没有正常退出,它的对象就一直都存在着,因为Greenlet还维护着一个链表,它保存着所有没有彻底退出的greenlet对象,『函数B』出栈并不是完全退出了,只是不参与这次行动。具体的细节就要等到下一篇或者下下篇的源码剖析来讲解了。

因为最近工作比较忙,换了一个部门,加上之前对源码理解不够,也就不敢随便发表。当然现在发表的也不敢说绝对正确,这些仅仅是我个人的理解,欢迎大神来指正。

相关文章
相关标签/搜索