背景知識:python
在Python中一個function要運行起來,它在python VM中須要三個東西。程序員
Python正是經過這三樣東西模擬0x86的函數調用的網絡
在python中 coroutine(協程)被稱爲的generator,這兩個東西在python實際上是同一個東東,之因此如此稱呼是由於它有迭代器的功能,可是又能夠只消耗不多的內存。不吃能存,又產生數據,稱爲generator仍是很符合情況的。數據結構
Python中的generotor是一種PyFunctionCode 和PyFrameObject的包裝,這個生成器是有本身獨立 value stack 的。在加上它能在執行function code的中途返回,而且保存PyFrameObject的狀態。因此就有相似線程的一個主要做用了:可以被調度。app
對於操做系統而言,它可以調度的只有線程,並且這種調度發生在內核態,調度時機對於程序員來講是不可知的。通常發生wait某個東西(鎖、網絡數據、磁盤數據)、時間片用完的時候,這個時候若是是非阻塞的返回,可是當前任務由於缺乏數據又不能繼續執行,做爲要榨乾CPU的程序員不能浪費掉分配到時間片,因此應該切換任務。若是一個線程表明一個任務的話,那麼在內核就多出一個線程對象。增長內存和調度程序的負擔,若是可以在用戶態有一種可以由程序員來控制調度的任務,便不用在內核態增長線程對象,任務調度由程序員負責。這個在用戶態能夠調度的東西就是coroutine了。由於能夠被切換,在一個線程內,它應該有本身的堆棧、本身寄存器(狀態)-------若是用C/C++這種語言實現的話,若是是在VM中實現,它在發生切換時,只要保持表明當前任務(其實就是函數)狀態的PyFrameObject的狀態就能夠了。函數
CPython generator涉及的數據結構和對象post
1.PyGen_Typespa
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')