协程

上次已经讲解了协程的的实现方法,和我对协程的一些理解。这里指我就先以代码说明协程的运行。
def test1():
    print 12         (2)
    gr2.switch()     (3)
    print 34         (6)

def test2():
    print 56         (4)
    gr1.switch()     (5)
    print 78         (8)

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()          (1)
gr2.switch()          (7)

输出结果为:
12
56
34
78
可以看到声明了两个协程gr1,gr2。通过协程的switch,进行协程切换。在代码后边,已经表明了协程代码执行过程。
可以比较明确的看到协程之间交互执行,这也是协程的名称的由来。他们协作进行进行工作。switch()是一个重要的方法,
它启动协程、切换协程。

协程实现

好了,开始我们的协程代码之旅。从协程的两个步骤:
Note: 如果你对Python的C extensions不是非常熟悉,建议你先看(https://docs.python.org/2/extending/extending.html)
1.创建协程(greenlet)
首先:初始化协程环境,将greenlet模块的各种状态(GreenMethods,模块变量ts_curkey等,各项Exception,当前运行ts_current,最终将这些都形成模块的
属性)请看initgreenlet。
一直到现在我们可以创建协程了,因为我们有了协程的这些Type。
创建协程,这个过程非常简单,greenlet(func)就创建了一个协程。PyGreenlet_Type(greenlet.c)已经说明了这两个方法(green_new,green_init)
green_new,创建了一个PyGreenlet,他们的parent设置为ts_current.(如果是协程A内有创建协程B,则ts_current就是协程A, B.parent就是A)

static PyObject* green_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject* o = PyBaseObject_Type.tp_new(type, ts_empty_tuple, ts_empty_dict);
if (o != NULL) {
if (!STATE_OK) {
Py_DECREF(o);
return NULL;
}
Py_INCREF(ts_current);
((PyGreenlet*) o)->parent = ts_current;
}
return o;
}

green_init,将参数进行分解,赋予self对应的PyGreenlet。

static int green_init(PyGreenlet *self, PyObject *args, PyObject *kwargs)
{
PyObject *run = NULL;
PyObject* nparent = NULL;
static char *kwlist[] = {"run", "parent", };
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OO:green", kwlist,
&run, &nparent))
return -; if (run != NULL) {
if (green_setrun(self, run, NULL))
return -;
}
if (nparent != NULL && nparent != Py_None)
return green_setparent(self, nparent, NULL);
return ;
}

是的很容易明白他们的意图。

2.协程切换(switch)
从green_switch->g_switch非常直接,进入协程的核心。协程切换(不需要关注协程运行环境,因为CPU整体,运行时状态的寄存器进行了切换和保存。)

g_switch(PyGreenlet* target, PyObject* args, PyObject* kwargs)
{
......(省略)
/* find the real target by ignoring dead greenlets,
and if necessary starting a greenlet. */
while (target) {
if (PyGreenlet_ACTIVE(target)) {
ts_target = target;
err = g_switchstack();
break;
}
if (!PyGreenlet_STARTED(target)) {
void* dummymarker;
ts_target = target;
err = g_initialstub(&dummymarker);
if (err == ) {
continue; /* retry the switch */
}
break;
}
target = target->parent;
}
................(省略)
}

首先就是获取真正执行的target,当然target分为两种情况,活跃的协程,没有开始的协程。按照我们的例子,gr1.switch()这个时候,
是为开始的协程,下边执行的是g_initialstub。(g_initialstu做了几件事情 保存当前协程的环境,将协程的栈空间保存到堆上,这个很简单,切换回来的时候
把堆上的运行空间复制回来就可以继续运行。不然就没有办法进行切换)

static int GREENLET_NOINLINE(g_initialstub)(void* mark)
{
........(省略)
//将协程的前后关系处理好,形成一个链
if (ts_current->stack_start == NULL) {
/* ts_current is dying */
self->stack_prev = ts_current->stack_prev;
}
else {
self->stack_prev = ts_current;
}
.....(省略)
//在g_switchstack.进行
/* perform the initial switch */
err = g_switchstack();
//纠正,这个返回twice是不正确的,返回两次,只有fork系统调用,因为完成之后,有两个进程执行。
//这个方法并没有返回两次。因为执行完成之后,就再次进行了切换。不应该认为返回两次
/* returns twice!
The 1st time with err=1: we are in the new greenlet
The 2nd time with err=0: back in the caller's greenlet
*/
if (err == ) {
/* in the new greenlet */
PyGreenlet* origin;
.........(省略) if (args == NULL) {
/* pending exception */
result = NULL;
} else {
//这个方法协程进行了真正的执行。
/* call g.run(*args, **kwargs) */
result = PyEval_CallObjectWithKeywords(
run, args, kwargs);
Py_DECREF(args);
Py_XDECREF(kwargs);
}
Py_DECREF(run);
result = g_handle_exit(result);
/* jump back to parent */
self->stack_start = NULL; /* dead */
}
// 完成之后,在进行切换
/* jump back to parent */
self->stack_start = NULL; /* dead */
for (parent = self->parent; parent != NULL; parent = parent->parent) {
result = g_switch(parent, result, NULL);
/* Return here means switch to parent failed,
* in which case we throw *current* exception
* to the next parent in chain.
*/
assert(result == NULL);
}
}
}

现在开始关心g_switchstack如何进行切换了。
g_switchstack执行了汇编slp_switch(),在这里我之查看x86汇编。

slp_switch(void)
{
/*将寄存器内容保存在协程的内存中,当恢复了协程内存之后,将内容恢复到寄存器中。新旧协程不会冲突。
协程切换,也就是将寄存器进行内容进行保存,将栈地址进行保存和恢复*/
int err;
void *ebp, *ebx;
unsigned short cw;
register int *stackref, stsizediff;
__asm__ volatile ("" : : : "esi", "edi");
__asm__ volatile ("fstcw %0" : "=m" (cw));
__asm__ volatile ("movl %%ebp, %0" : "=m" (ebp));
__asm__ volatile ("movl %%ebx, %0" : "=m" (ebx));
__asm__ ("movl %%esp, %0" : "=g" (stackref));
{
SLP_SAVE_STATE(stackref, stsizediff);
__asm__ volatile (
"addl %0, %%esp\n"
"addl %0, %%ebp\n"
:
: "r" (stsizediff)
);
SLP_RESTORE_STATE();
__asm__ volatile ("xorl %%eax, %%eax" : "=a" (err));
}
__asm__ volatile ("movl %0, %%ebx" : : "m" (ebx));
__asm__ volatile ("movl %0, %%ebp" : : "m" (ebp));
__asm__ volatile ("fldcw %0" : : "m" (cw));
__asm__ volatile ("" : : : "esi", "edi");
return err;
}

在汇编代码中,看到的内容就是保存、恢复寄存器和保存协程状态,恢复协程状态。

下边来看以下如何保存状态的SLP_SAVE_STATE-> slp_save-state

static int GREENLET_NOINLINE(slp_save_state)(char* stackref)
{
    /* must free all the C stack up to target_stop */
    char* target_stop = ts_target->stack_stop;
    PyGreenlet* owner = ts_current;
    assert(owner->stack_saved == 0);
    if (owner->stack_start == NULL)
        owner = owner->stack_prev;  /* not saved if dying */
    else
        owner->stack_start = stackref;     while (owner->stack_stop < target_stop) {
        //owner的结束地址小于target结束地址,产生了栈覆盖
        /* ts_current is entierely within the area to free */
        if (g_save(owner, owner->stack_stop))
            return -1;  /* XXX */
        owner = owner->stack_prev;
    }
    if (owner != ts_target) {
        if (g_save(owner, target_stop))
            return -1;  /* XXX */
    }
    return 0;
} static int g_save(PyGreenlet* g, char* stop)
{
/* Save more of g's stack into the heap -- at least up to 'stop'
g->stack_stop |________|
| |
|_ _ _ _ stop . . . . .
| | ==> . .
|________| _______
| | | |
| | | |
g->stack_start |________| |_______| g->stack_copy intptr_t sz1 = g->stack_saved;
intptr_t sz2 = stop - g->stack_start;
assert(g->stack_start != NULL);
if (sz2 > sz1) {
char* c = (char*)PyMem_Realloc(g->stack_copy, sz2);
if (!c) {
PyErr_NoMemory();
return -1;
}
memcpy(c+sz1, g->stack_start+sz1, sz2-sz1);
g->stack_copy = c;
g->stack_saved = sz2;
}
return 0;
}

这就是协程栈的保存,申请内存,然后将栈memcpy进入去。

恢复,也是类似,将保存进内存,拷贝到栈上。然后将寄存器进行修改

不过有点特殊的事情是:python看不到eip(指令指针,执行下一条运行指令),而python对应的内容,在frameobject中,
有PyCodeObject,而执行的时候,在ceval.c可以看到是通过next_instr来获取python运行指令。
所以协程有top_frame,就是为了切换协程时候,对指令集合进行切换

代码中有使用PyThreadState,但是并没有用thread线程,对于PyThreadState的使用只是为了,方便的让run_info,这个函数指针,
进行处理成为各项frameobject,recursion_depth等内容。

注:
1.对于中间的汇编,还是建议好好的看一下。我刚好看了中国科技大学-孟宁出的一个视频教程《linux 内核解读》刚好对汇编有非常好的讲解。

2.而协程运行之后,greenlet的栈布局如下:(图片来源于网络)

greenlet代码解读的更多相关文章

  1. Android MVP模式 谷歌官方代码解读

    Google官方MVP Sample代码解读 关于Android程序的构架, 当前(2016.10)最流行的模式即为MVP模式, Google官方提供了Sample代码来展示这种模式的用法. Repo ...

  2. 优秀开源代码解读之JS与iOS Native Code互调的优雅实现方案

    简介 本篇为大家介绍一个优秀的开源小项目:WebViewJavascriptBridge. 它优雅地实现了在使用UIWebView时JS与ios 的ObjC nativecode之间的互调,支持消息发 ...

  3. SoftmaxLayer and SoftmaxwithLossLayer 代码解读

    SoftmaxLayer and SoftmaxwithLossLayer 代码解读 Wang Xiao 先来看看 SoftmaxWithLoss 在prototext文件中的定义: layer { ...

  4. Hybrid----优秀开源代码解读之JS与iOS Native Code互调的优雅实现方案-备

    本篇为大家介绍一个优秀的开源小项目:WebViewJavascriptBridge. 它优雅地实现了在使用UIWebView时JS与ios 的ObjC nativecode之间的互调,支持消息发送.接 ...

  5. Jsoup代码解读之六-防御XSS攻击

    Jsoup代码解读之八-防御XSS攻击 防御XSS攻击的一般原理 cleaner是Jsoup的重要功能之一,我们常用它来进行富文本输入中的XSS防御. 我们知道,XSS攻击的一般方式是,通过在页面输入 ...

  6. Jsoup代码解读之五-实现一个CSS Selector

    Jsoup代码解读之七-实现一个CSS Selector 当当当!终于来到了Jsoup的特色:CSS Selector部分.selector也是我写的爬虫框架webmagic开发的一个重点.附上一张s ...

  7. Jsoup代码解读之四-parser

    Jsoup代码解读之四-parser 作为Java世界最好的HTML 解析库,Jsoup的parser实现非常具有代表性.这部分也是Jsoup最复杂的部分,需要一些数据结构.状态机乃至编译器的知识.好 ...

  8. Jsoup代码解读之三-Document的输出

    Jsoup代码解读之三-Document的输出   Jsoup官方说明里,一个重要的功能就是output tidy HTML.这里我们看看Jsoup是如何输出HTML的. HTML相关知识 分析代码前 ...

  9. Jsoup代码解读之一-概述

    Jsoup代码解读之一-概述 今天看到一个用python写的抽取正文的东东,美滋滋的用Java实现了一番,放到了webmagic里,然后发现Jsoup里已经有了…觉得自己各种不靠谱啊!算了,静下心来学 ...

随机推荐

  1. ssl error rx record too long

  2. 问题-"Record not found or changed by another user"

    回答1:===============================================================问题:clientdataset“Record not found ...

  3. JavaScript- The Good Parts function Curry

    Functions are values, and we can manipulate function values in interesting ways.Currying allows us t ...

  4. UNITY 打包安卓APK

    1,安装JDK.这个直接下就行了. 2,安装android sdk相关.这个比较蛋疼,官网是被墙的.有些网站的包还是需要访问墙外下载的.关键是找对那个能用的包(对我来说就是不FQ). http://p ...

  5. (二 )VMware workstation 部署虚拟集群实践——并行批量操作环境部署

    在上一篇博客中,已经介绍了安装虚拟集群的过程和需要注意的细节问题. 这篇主要是介绍如何批量登陆远程主机和配置,这个过程中是在没有部署并行处理工具或者集群管理工具的前进行的. ------------首 ...

  6. POJ 3670 , 3671 LIS

    题意:两题意思差不多,都是给你一个序列,然后求最少需要改变多少个数字,使得成为一个最长不升,或者最长不降子序列. 当然3671是只能升序,所以更简单一点. 然后就没有什么了,用二分的方法求LIS即可. ...

  7. [React] React Fundamentals: Component Lifecycle - Updating

    The React component lifecycle will allow you to update your components at runtime. This lesson will ...

  8. careercup-树与图 4.5

    4.5 实现一个函数,检查一棵二叉树是否为二叉查找树. 参考:http://blog.csdn.net/sgbfblog/article/details/7771096 C++实现代码: #inclu ...

  9. Android(java)学习笔记160:Framework运行环境之 Android进程产生过程

    1.前面Android(java)学习笔记159提到Dalvik虚拟机启动初始化过程,就下来就是启动zygote进程: zygote进程是所有APK应用进程的父进程:每当执行一个Android应用程序 ...

  10. 微信公众号支付(一):获取用户openId

    一.获取apikey,appsecret与商户号 注册公众号.商户号 二.获取用户的OpenId 1.设置[授权回调页面域名] 官方解释:用户在网页授权页同意授权给公众号后,微信会将授权数据传给一个回 ...