背景知识:
在Python中一个function要运行起来,它在python VM中需要三个东西。
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)浅析和应用
原文:http://www.cnblogs.com/hi0xcc/p/5588966.html