greenlet implemenation

greenlet是一个python coroutine库,可以提供比thread更加细粒度的并发控制。由于thread通常是一个内核对象,而coroutine通常只是工作在用户模式下,所以不存在线程切换时的开销。当使用coroutine和thread来实现并发时,将会得到更大的并发度。因为thread虽然比起process而言已经十分轻量,但是毕竟还是有开销的,当系统中有太多的thread在运行,也会拖累系统的运行速度,因为操作系统忙于执行context switch。

在python中,我们从thread中说获得并发度要比想象的低很多,因为thread其实不是完全并发执行的,这其中涉及到一个GIL(Global Interpreter Lock)的问题。关于GIL的问题,可以参考Understanding the Python GIL。

现在来简单讲解一下greenlet的实现:

switch to a new greenlet
stack overview

  high  +----------------+
   ^    |    old data    |
   |    +----------------+
   |    |    g_switch    |
   |    |----------------|
   |    |  dummy marker  |
   |    |----------------| <------- stack_stop  _
   |    |   other data   |                       /
   |    +----------------+                       |
   |    |  g_initialstub |                       |
   |    +----------------+                       |
   |    |  g_switchstack |                       |
   |    +----------------+                       /__ greenlet stack
   |    |   slp_switch   |                       /
   |    |----------------|                       |
   |    |   stsizediff   |                       |
   |    |----------------|                       |
   |    |    stackref    |                       |
   |    +----------------+ <------- stack_start _/
   |    | slp_save_state |
   |    +----------------+
   |    |     g_save     |
  low   +----------------+

在调用slp_save_state保存了target greenlet stack后, slp_switch会返回1, 此时控制流返回到g_initialstub.

此时会去调用PyEval_CallObjectWithKeywords, 调用greenlet的run函数, 从该函数返回后,将会切换到parent greenlet. 通常情况下,如果我们在创建greenlet时不指定parent,那么默认就是当前运行的greenlet: ts_current (green_new).
切换时,调用g_switch,并且将run的返回值传递给switch作为返回值。

当我们启动一个新的greenlet时,新的greenlet的stack必然在已有的greenlet之下。

running in a greenlet B, which is started by greenlet A, and try to switch to greenlet A.
stack overview

   high  +---------------+
    ^    |    old data   |
    |    +---------------+
    |    |    g_switch   |
    |    |---------------|
    |    |     marker    |
    |    +---------------+
    |    | g_initialstub |
    |    +---------------+
    |    |    call run   |
    |    +---------------+
    |    |    ... ...    |
    |    +---------------+
    |    |   g_switch    |
    |    |---------------|
    |    |     marker    |
    |    +---------------+
    |    | g_initialstub |
    |    +---------------+
    |    |    call run   |
    |    +---------------+
    |    |    ... ...    |
    |    +---------------+
    |    |    g_switch   |
    |    +---------------+
    |    | g_switchstack |
    |    +---------------+
    |    |   slp_switch  |
    |    |---------------|
    |    |   stsizediff  |
    |    |---------------|
    |    |    stackref   |
    |    +---------------+
    |    | slp_save_state|
    |    =================
   low

在切换到一个已经运行的greenlet时,slp_save_state会save更多的东西。原来切换到一个新的greenlet,只需要save prev greenlet(通常是calling greenlet)的状态. 而现在,由于已经有多个calling greenlet运行,所以必须依次save这些greenlets.

当切换到一个greenlet A,A.stack_stop < ts_current.stack_start时,在restore A的stack后,会在当中产生一段空档。

比如A->B->C,C switch to A, 此时stack中只有A,之后A switch to C,此时C中save的stack_copy会与A的stack产生一个空挡,其中这个空档就是原来的B造成的。

 

return from a greenlet
在调用完slp_save_state后,由于切换到的是一个已经运行的greenlet,所以我们会接着调用

    stsizediff = ts_target->stack_start - (char*)stackref

这里计算stack的差,因为在跳转到target greenlet后,我们target的stack必须是正确的。
还记得stackref吗,这个值是我们在调用到slp_switch时,esp的值,也就是说,我们每次slp_save_state的栈,其实是save到slp_switch为止的。

接着

    mov     eax, stsizediff
    add     esp, eax
    add     ebp, eax

这里将esp和ebp恢复到target greenlet调用slp_switch的状态。

之后,再调用slp_restore_state

    memcpy(g->stack_start, g->stack_copy, g->stack_saved);
    PyMem_Free(g->stack_copy);
    g->stack_copy = NULL;
    g->stack_saved = 0;

这里的g是target greenlet, 这里将heap中的stack copy恢复到原来的位置上
restore后,heap中的stack被free
也就是说,只要是正在运行的greenlet(ts_current),必定没有stack_copy.

当从slp_switch返回后,我们设置了thread state,把frame指向目标的top_frame,这样就达到了恢复python中ip指针的目的。

这里很重要的一点是,slp_save_state 和 slp_restore_state在同一个函数里。因为我们save的stack的状态包括slp_switch的stack frame,当然restore之后,我们也就必须在这个函数中恢复执行才可以。

 

switch to self

greenlet.getcurrent可以获得当前的greenlet,这样我们不必显式传递greenlet。当switch to self时,其实是一个no-op。

 

调用g_switch之后,由于切换到自身,所以PyGreenlet_ACTIVE返回true, 我们进入g_switchstack, 随后进入到slp_switch,这里会接着调用slp_save_state。

    while (ts_current->stack_stop < target_stop)
    {
        /* ts_current is entierely within the area to free */
        if (g_save(ts_current, ts_current->stack_stop))
            return -1;  /* XXX */
        ts_current = ts_current->stack_prev;
    }
    if (ts_current != ts_target)
    {
        if (g_save(ts_current, target_stop))
            return -1;  /* XXX */
    }

由于ts_current = ts_target,所以不会进入while和if,于是state不会被save,这是正确的,因为当前正在运行的greenlet的stack_copy = NULL。

随后进入到slp_restore_state

    if (ts_current->stack_stop == g->stack_stop)
        g->stack_prev = ts_current->stack_prev;
    else
        g->stack_prev = ts_current;

由于ts_current = g = ts_target,所以这里还是个no-op

接着返回到g_switchstack

    tstate->frame = ts_target->top_frame;

这里恢复thread state的frame,还是一个no-op,最后返回到g_switch,函数返回switch参数,结束。

 

你可能感兴趣的:(greenlet implemenation)