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:

  • 相关阅读:
    MySQL InnoDB 锁
    MySQL InnoDB 事务
    MySQL 执行计划详解
    php获取当前url地址的方法小结
    数据库联表统计查询 Group by & INNER JOIN
    大文件分片上传,断点续传,秒传 实现
    如何让 height:100%; 起作用
    移动前端头部标签(HTML5 head meta)
    Emoji表情符号在MySQL数据库中的存储
    HTML页面直接显示json 结构
  • 原文地址:https://www.cnblogs.com/tsecer/p/10419533.html
Copyright © 2011-2022 走看看