zoukankan      html  css  js  c++  java
  • 从generator的send接口参数看python的异步机制

    一、generator的生成

    当解析一个函数的时候,在词法分析阶段,如果发现函数中使用了yield的指令,则会将函数设置为一个特殊的CO_GENERATOR标志,表示这个函数是一个生成。这个标志对于生成器的识别非常重要,因为在对一个函数执行CALLFUNCTION的时候,判断如果一个函数有这个标志,则并不会真正的执行这个函数,而是在这个函数的基础上生成一个新的generator函数。这个步骤对于生成generator来说至关重要,因为对于常规函数执行CALLFUNCTION指令,它每次在虚拟机内部都生成一个新的PyFrameObject对象实例,这种缺省的行为对于generator来说是不能接收的,因为generator/coroutine要保留上次执行时的栈帧信息,从而可以再次执行的时候可以完美继续。
    相反,当我们使用一个generator/coroutine来封装这个栈帧的时候,这个封装类就可以任意的操作这个保留有上下文的PyFrameObject对象,而这个正是generator/coroutine所必须的实现功能。

    1、执行CALLFUNCTION虚拟机指令时解释器的特殊处理

    下面是在执行CALLFUNCTION虚拟机指令时对于CO_GENERATOR的处理逻辑
    static PyObject *
    _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
    PyObject **args, Py_ssize_t argcount,
    PyObject **kwnames, PyObject **kwargs,
    Py_ssize_t kwcount, int kwstep,
    PyObject **defs, Py_ssize_t defcount,
    PyObject *kwdefs, PyObject *closure,
    PyObject *name, PyObject *qualname)
    {
    ……
    /* Handle generator/coroutine/asynchronous generator */
    if (co->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) {
    PyObject *gen;
    PyObject *coro_wrapper = tstate->coroutine_wrapper;
    int is_coro = co->co_flags & CO_COROUTINE;

    if (is_coro && tstate->in_coroutine_wrapper) {
    assert(coro_wrapper != NULL);
    PyErr_Format(PyExc_RuntimeError,
    "coroutine wrapper %.200R attempted "
    "to recursively wrap %.200R",
    coro_wrapper,
    co);
    goto fail;
    }

    /* Don't need to keep the reference to f_back, it will be set
    * when the generator is resumed. */
    Py_CLEAR(f->f_back);

    PCALL(PCALL_GENERATOR);

    /* Create a new generator that owns the ready to run frame
    * and return that as the value. */
    if (is_coro) {
    gen = PyCoro_New(f, name, qualname);
    } else if (co->co_flags & CO_ASYNC_GENERATOR) {
    gen = PyAsyncGen_New(f, name, qualname);
    } else {
    gen = PyGen_NewWithQualName(f, name, qualname);
    }
    if (gen == NULL)
    return NULL;

    if (is_coro && coro_wrapper != NULL) {
    PyObject *wrapped;
    tstate->in_coroutine_wrapper = 1;
    wrapped = PyObject_CallFunction(coro_wrapper, "N", gen);
    tstate->in_coroutine_wrapper = 0;
    return wrapped;
    }

    return gen;
    }

    retval = PyEval_EvalFrameEx(f,0);
    ……
    }

    2、从function对象到generator对象

    对于基础的generator对象,其中调用PyGen_NewWithQualName(f, name, qualname)来生成一个generator对象,所以当我们调用一个函数的时候,它返回的是一个generator对象。或者再简单的说,可以认为是解析器根本没有执行函数调用,只是把函数调用封装在一个对象中返回给调用者。是不是感觉很诡异?
    tsecer@harry: cat itergen.py
    def geniter(x):
    for i in range(x):
    print(i)
    yield i
    print(geniter(10))
    tsecer@harry: ../../Python-3.6.0/python itergen.py
    <generator object geniter at 0x7fa8bea1b960>
    tsecer@harry:
    在python内部,generator实现了gen_iternext接口,并且在gen_methods中定义了next接口
    PyTypeObject PyGen_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "generator", /* tp_name */
    ……
    PyObject_SelfIter, /* tp_iter */
    (iternextfunc)gen_iternext, /* tp_iternext */
    gen_methods, /* tp_methods */
    gen_memberlist, /* tp_members */
    gen_getsetlist, /* tp_getset */
    ……
    }
    两个接口的定义:
    static PyObject *
    gen_iternext(PyGenObject *gen)
    {
    return gen_send_ex(gen, NULL, 0, 0);
    }

    PyObject *
    _PyGen_Send(PyGenObject *gen, PyObject *arg)
    {
    return gen_send_ex(gen, arg, 0, 0);
    }

    二、yield对应的解释器指令

    1、虚拟机指令

    python脚本文件及对应的虚拟机指令
    tsecer@harry: cat itersend.py
    def geniter(x):
    for x in range(x):
    y = yield x
    print(y)
    iterinst = geniter(10)
    dir(iterinst)
    next(iterinst)
    #iterinst()
    print(iterinst.send(None))
    geniter函数中yield对应的虚拟机指令
    >>> dis.dis(itersend.geniter)
    2 0 SETUP_LOOP 30 (to 32)
    2 LOAD_GLOBAL 0 (range)
    4 LOAD_FAST 0 (x)
    6 CALL_FUNCTION 1
    8 GET_ITER
    >> 10 FOR_ITER 18 (to 30)
    12 STORE_FAST 0 (x)

    3 14 LOAD_FAST 0 (x)
    16 YIELD_VALUE
    18 STORE_FAST 1 (y)

    4 20 LOAD_GLOBAL 1 (print)
    22 LOAD_FAST 1 (y)
    24 CALL_FUNCTION 1
    26 POP_TOP
    28 JUMP_ABSOLUTE 10
    >> 30 POP_BLOCK
    >> 32 LOAD_CONST 0 (None)
    34 RETURN_VALUE
    >>>
    tsecer@harry:

    2、虚拟机指令的执行

    可以看到,这里主要操作是保留堆栈栈顶的指针,并且把yield指令的结果赋值给retval,然后结束虚拟机指令的继续执行。
    TARGET(YIELD_VALUE) {
    retval = POP();

    if (co->co_flags & CO_ASYNC_GENERATOR) {
    PyObject *w = _PyAsyncGenValueWrapperNew(retval);
    Py_DECREF(retval);
    if (w == NULL) {
    retval = NULL;
    goto error;
    }
    retval = w;
    }

    f->f_stacktop = stack_pointer;
    why = WHY_YIELD;
    goto fast_yield;
    }

    三、挂起之后如何继续

    从前面的实现看,在generator/coroutine中都定义了send接口,该接口可以恢复函数继续执行。那么send这个接口中arg的作用是干什么用的呢?从python中对于该接口的文档描述可以知道它的大致功能为
    "send(arg) -> send 'arg' into generator,\n\
    return next yielded value or raise StopIteration.");
    这个描述依然比较模糊,所以继续看下send接口具体执行了什么:
    static PyObject *
    gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
    {
    ……
    } else {
    /* Push arg onto the frame's value stack */
    result = arg ? arg : Py_None;
    Py_INCREF(result);
    *(f->f_stacktop++) = result;
    }
    ……
    }
    可以看到是把参数压入了挂起Frame的堆栈顶端,而python的局部参数传递都是基于堆栈操作的。再看下前面的python代码及对应的虚拟机指令
    y = yield x

    y = yield x * x
    那么这里生成的虚拟机指令
    3 14 LOAD_FAST 0 (x)
    16 YIELD_VALUE
    18 STORE_FAST 1 (y)
    在执行STORE_FAST的动作
    PREDICTED(STORE_FAST);
    TARGET(STORE_FAST) {
    PyObject *value = POP();
    SETLOCAL(oparg, value);
    FAST_DISPATCH();
    }
    首先从从栈顶取出数值,而这个值就是send(arg)压入的arg对象,也就是y的值被替换为了arg。
    下面代码的执行结果
    tsecer@harry: cat itersend.py
    def geniter(x):
    for x in range(x):
    y = yield x
    print(y)
    iterinst = geniter(10)
    dir(iterinst)
    next(iterinst)
    #iterinst()
    print(iterinst.send(None))
    tsecer@harry: ../../Python-3.6.0/python itersend.py
    None
    1
    tsecer@harry:

  • 相关阅读:
    Apache 虚拟主机 VirtualHost 配置
    EAX、ECX、EDX、EBX寄存器的作用
    Python中文文档 目录(转载)
    八度
    POJ 3268 Silver Cow Party (最短路)
    POJ 2253 Frogger (求每条路径中最大值的最小值,Dijkstra变形)
    2013金山西山居创意游戏程序挑战赛——复赛(1) HDU 4557 非诚勿扰 HDU 4558 剑侠情缘 HDU 4559 涂色游戏 HDU 4560 我是歌手
    HDU 4549 M斐波那契数列(矩阵快速幂+欧拉定理)
    UVA 11624 Fire! (简单图论基础)
    HDU 3534 Tree (树形DP)
  • 原文地址:https://www.cnblogs.com/tsecer/p/10419533.html
Copyright © 2011-2022 走看看