背景知识:

  在Python中一个function要运行起来,它在python VM中需要三个东西。

  1. PyCodeObject,这个保存了函数的代码
  2. PyFunctionObject,这个代表一个虚拟机中的一个函数对象
  3. PyFrameObject,这个代表了函数运行时的调用链和堆栈

   Python正是通过这三样东西模拟0x86的函数调用的

  在python中 coroutine(协程)被称为的generator,这两个东西在python其实是同一个东东,之所以如此称呼是因为它有迭代器的功能,但是又可以只消耗很少的内存。不吃能存,又产生数据,称为generator还是很符合状况的。

  Python中的generotor是一种PyFunctionCode 和PyFrameObject的包装,这个生成器是有自己独立 value stack 的。在加上它能在执行function code的中途返回,并且保存PyFrameObject的状态。所以就有类似线程的一个主要作用了:能够被调度。

  对于操作系统而言,它能够调度的只有线程,而且这种调度发生在内核态,调度时机对于程序员来说是不可知的。一般发生wait某个东西(锁、网络数据、磁盘数据)、时间片用完的时候,这个时候如果是非阻塞的返回,但是当前任务因为缺少数据又不能继续执行,作为要榨干CPU的程序员不能浪费掉分配到时间片,所以应该切换任务。如果一个线程代表一个任务的话,那么在内核就多出一个线程对象。增加内存和调度程序的负担,如果能够在用户态有一种能够由程序员来控制调度的任务,便不用在内核态增加线程对象,任务调度由程序员负责。这个在用户态可以调度的东西就是coroutine了。因为可以被切换,在一个线程内,它应该有自己的堆栈、自己寄存器(状态)-------如果用C/C++这种语言实现的话,如果是在VM中实现,它在发生切换时,只要保持代表当前任务(其实就是函数)状态的PyFrameObject的状态就可以了。


CPython generator涉及的数据结构和对象

1.PyGen_Type

PyTypeObject PyGen_Type = {    PyVarObject_HEAD_INIT(&PyType_Type, 0)
"generator", /* tp_name */
sizeof(PyGenObject), /* tp_basicsize */
.........省略
PyObject_GenericGetAttr, /* tp_getattro */
   ....... 省略
(traverseproc)gen_traverse, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
offsetof(PyGenObject, gi_weakreflist), /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
(iternextfunc)gen_iternext, /* tp_iternext */
gen_methods, /* tp_methods */
gen_memberlist, /* tp_members */
gen_getsetlist, /* tp_getset */
.......省略
gen_del, /* tp_del */
}; 

  从PyGen_Type这个对象对tp_iter,tp_iternext的设置来看,说明generator是实现了iterator protocol了,可以在for 语句中迭代它。

2.PyCodeObject、PyFrameObject,PyFunctionObject

3.PyGenObject

typedef struct {
PyObject_HEAD
/* The gi_ prefix is intended to remind of generator-iterator. */
/* Note: gi_frame can be NULL if the generator is "finished" */
//PyFrameObject
struct _frame *gi_frame; /* True if generator is being executed. */
//状态
int gi_running;
/* The code object backing the generator */
//PyCodeObject
PyObject *gi_code;
/* List of weak reference. */
PyObject *gi_weakreflist;
} PyGenObject;


PyGenObject中的gi_running表示状态 0:没有正在运行,1:正在运行,用frame.f_lasti==-1表示没有启动过,因为没有运行过bytecode,所以frame的last instuction offset 会是-1,gi_code对应generator的方法代码,gi_frame为PyFrameObject,用于保存当前generator字节码执行的状态,可以知道generator只能对应一个Frame,它不肯有嵌套的Frame了,也就是不能在generator调用的函数中返回到send/next点,这个对与它的应用来说,会是一个限制,如果业务复杂会导致generator的代码比较臃肿。

CPython 中generator的实现分析:

以这段python代码为分析对象

def gen():
x=yield 1
print x
x=yield 2 g=gen() g.next()
print g.send("sender")

  对应的Python bytecode为

源码行号 python代码 字节码偏移 字节码 字节码参数 注释
1 def gen(): 0 LOAD_CONST

0 (<code object gen )

这里定义了一个PyFunctionObject,

对应的PyCodeObject

有一个flag(CO_GENERATOR)

标记是一个generator

    3 MAKE_FUNCTION 0  
    6 STORE_NAME 0(gen) gen=PyFunctionObject 
           
7 g=gen() 9 LOAD_NAME 0(gen)   
    12 CALL_FUNCTION  

在PyEval_EvalCodeEX中,因为gen保存的

PyFunctionObject,

对应的PyCodeObject.co_flags

有CO_GENERATOR标记,

它直接返回返回一个PyGenObject

    15 STORE_NAME 1(g)  
           
 9  g.next() 18  LOAD_NAME  1(g)  
     21  LOAD_ATTR  2 (next)

PyObject_GetAttr(g,'next')

PyGen_Type.tp_getattro()

此时tp_getattro=PyObject_GenericGetAttr

得到wrappertype

这个wrapper包含了generator,

     24  CALL_FUNCTION  0

在call 的时候,转而调用 generator.next

就是gen_iternext,之后转到

gen_send_ex这里,

     27  POP_TOP    
           
 10    28  LOAD_NAME  1 (g)  
     31  LOAD_ATTR  3 (send)  
     34  LOAD_CONST  1 ('sender')  
     37  CALL_FUNCTION  1

这里转到

gen_send(PyGenObject *gen,

    PyObject *arg)

     40  PRINT_ITEM    
     41  PRINT_NEWLINE    
     42  LOAD_CONST  2 (None)  
     45  RETURN_VALUE    
           

在分析CPython源码的时候会遇到许多的PyMethodDescrObject、PyMemberDescrObject、PyGetSetDescrObject、PyWrapperDescrObject,是因为Python语言设计的比较灵活,不同的方法、属性,有不同的获取方法,另外不同的方法有不同的参数,所以调用的方式也不一样啊,所以对应的C代码应该有不同的策略,需要包装起到这个策略作用。这些Descr都是一些外层的包装对象,只是为了方便管理而已。在class object初始化的时候保存到相应的type.tp_dict中.

coroutine的应用:

coroutine因为得不到操作系统的主动调用,要有程序员来控制调度时机,在用户态的调度不适合模拟实时的状体,但是非常适合做成无关时间的状态改变,我们以电商快递商品过程的为例,一个商品在卖家到达买家大致会经历下面几个状态:待售、已售、商品在起始城市、商品在中间城市、商品到达目的城市、开始投递、到达买家手中。

快递商品状态转换图

电商商品状态切换伪代码:

from collections import namedtuple

State=namedtuple('State','statename action')

def commodity(id):
#待售状态
action=yield State('forsale','online') #已售状体
if action=='sellout':
action =yield State('sellout','postman1')
elif action=='offline':
return #在出发城市快递点状态
if action=='store1':
action=yield State('store1','store in garage')
else:
return #已产生中间路径状态
middleCities=generateRoute(id)
if action=='route':
action=yield State('store1_routed','caculate route')
else:
return l=len(middleCities)
for city in middleCities:
if action=='next':
if city==middleCities[l-1]:
#已经到达目的城市状态
action =yield State('destination',city)
else:
#中间城市流转状态
action=yield State('middle_city',city) #在目的城市开始投递状态
if 'deliver':
action=yield State('delivering','postman is delivering')
else:
return
#被买家接受状态
if action=='accept':
yield State('accepted','finish')

  

python中的generator(coroutine)浅析和应用的更多相关文章

  1. python中生成器generator

    通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素 ...

  2. (转)Python中的generator详解

    本文转自:http://www.cnblogs.com/xybaby/p/6322376.html 作者:xybaby 注:本文在原文基础上做了一点点修改,仅仅作为个人理解与记忆,建议直接查看原文. ...

  3. python中的generator, iterator, iterabel

    先来看看如果遇到一个对象,如何判断其是否是这三种类型: from types import GeneratorType from collectiuons import Iterable, Itera ...

  4. Python中生成器generator和迭代器Iterator的使用方法

    一.生成器 1. 生成器的定义 把所需要值得计算方法储存起来,不会先直接生成数值,而是等到什么时候使用什么时候生成,每次生成一个,减少计算机占用内存空间 2. 生成器的创建方式 第一种只要把一个列表生 ...

  5. Python中optparse模块使用浅析

    转载:http://www.jb51.net/article/59296.htm 最近遇到一个问题,是指定参数来运行某个特定的进程,这很类似Linux中一些命令的参数了,比如ls -a,为什么加上-a ...

  6. python中的生成器(generator)总结

    1.实现generator的两种方式 python中的generator保存的是算法,真正需要计算出值的时候才会去往下计算出值.它是一种惰性计算(lazy evaluation). 要创建一个gene ...

  7. Python中print()函数不换行的方法

    一.让print()函数不换行 在Python中,print()函数默认是换行的.但是,在很多情况下,我们需要不换行的输出(比如在算法竞赛中).那么,在Python中如何做到这一点呢? 其实很简单.只 ...

  8. Python中yield函数浅析

    带有yield的函数在Python中被称之为generator(生成器),下面我们将使用斐波那契数列来举例说明下该函数:(环境是在Python3.x下)  如何生成斐波那契数列: 斐波那契(Fibon ...

  9. 浅析 PHP 中的 Generator

    浅析 PHP 中的 Generator Miss Wang php开发案例 前天 何为 Generator 从 PHP 5.5 开始,PHP 加入了一个新的特性,那就是 Generator,中文译为生 ...

随机推荐

  1. Python 2.7 - CentOS 7 - ImportError: No module named Tkinter

    It's simple. sudo yum -y install tkinter Just want to say, "I'm back".

  2. 解决警告“ld: warning: directory not found for option

    因为已经把文件编译到项目中,删除的话会出现找不到文件或文件夹的警告. 1选择工程, 编译的 (targets) 2选择 Build Settings 菜单 3查找 Library Search Pat ...

  3. Spring 核心框架体系结构

    转载:http://www.admin10000.com/document/10447.html 很多人都在用spring开发java项目,但是配置maven依赖的时候并不能明确要配置哪些spring ...

  4. 自动打开Accesibility Service 可以自动安装APP

    package com.venscor.helloworld;import java.io.BufferedReader;import java.io.IOException;import java. ...

  5. 记在virtualbox下挂载共享文件夹的方法

    sudo mount -t vboxsf share /usr/share sudo mount -t vboxsf 共享文件夹名称(在设置页面设置的) 挂载的目录

  6. varnish 隐藏版本号

    varnish 隐藏方法: 修改default.vcl配置文件. 找到或添加 vcl_deliver 子程序,代码如下: 1 2 3 4 5 sub vcl_deliver {        unse ...

  7. 用python实现计算1-2*((60-30+(-40/5)*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))类似的公式计算

    作业需求: 开发一个简单的python计算器 1.实现加减乘除及拓号优先级解析 2.用户输入 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 ...

  8. postgresql查询的处理过程

    本文简单描述了Postgresql服务器接收到查询后到返回给客户端结果之间一般操作顺序,总的流程图如下: 第一步: 客户端程序可以是任何符合 PostgreSQL 协议规范的程序,如 JDBC 驱动. ...

  9. JS中generater和箭头函数

    generater跟函数很像: function* fn(x){ yield x; yield x++; return x;} 如上所示,generater用function*定义,可以用yield返 ...

  10. H5新特性 input type=date 在手机上默认提示显示无效解决办法

    目前PC端对input 的date类型支持不好,我试下来的结果是只有chrome支持.firefox.IE11 都不支持.而且PC端有很多日历控件可供使用.就不去多考虑这点了. 那么在移动端的话,io ...