協程html
上次已經講解了協程的的實現方法,和我對協程的一些理解。這裏指我就先以代碼說明協程的運行。
def test1():
print 12 (2)
gr2.switch() (3)
print 34 (6)
def test2():
print 56 (4)
gr1.switch() (5)
print 78 (8)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch() (1)
gr2.switch() (7)
輸出結果爲:
12
56
34
78
能夠看到聲明瞭兩個協程gr1,gr2。經過協程的switch,進行協程切換。在代碼後邊,已經代表了協程代碼執行過程。
能夠比較明確的看到協程之間交互執行,這也是協程的名稱的由來。他們協做進行進行工做。switch()是一個重要的方法,
它啓動協程、切換協程。python
協程實現linux
好了,開始咱們的協程代碼之旅。從協程的兩個步驟:
Note: 若是你對Python的C extensions不是很是熟悉,建議你先看(https://docs.python.org/2/extending/extending.html)
1.建立協程(greenlet)
首先:初始化協程環境,將greenlet模塊的各類狀態(GreenMethods,模塊變量ts_curkey等,各項Exception,當前運行ts_current,最終將這些都造成模塊的
屬性)請看initgreenlet。
一直到如今咱們能夠建立協程了,由於咱們有了協程的這些Type。
建立協程,這個過程很是簡單,greenlet(func)就建立了一個協程。PyGreenlet_Type(greenlet.c)已經說明了這兩個方法(green_new,green_init)
green_new,建立了一個PyGreenlet,他們的parent設置爲ts_current.(若是是協程A內有建立協程B,則ts_current就是協程A, B.parent就是A)網絡
static PyObject* green_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject* o = PyBaseObject_Type.tp_new(type, ts_empty_tuple, ts_empty_dict); if (o != NULL) { if (!STATE_OK) { Py_DECREF(o); return NULL; } Py_INCREF(ts_current); ((PyGreenlet*) o)->parent = ts_current; } return o; }
green_init,將參數進行分解,賦予self對應的PyGreenlet。函數
static int green_init(PyGreenlet *self, PyObject *args, PyObject *kwargs) { PyObject *run = NULL; PyObject* nparent = NULL; static char *kwlist[] = {"run", "parent", 0}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OO:green", kwlist, &run, &nparent)) return -1; if (run != NULL) { if (green_setrun(self, run, NULL)) return -1; } if (nparent != NULL && nparent != Py_None) return green_setparent(self, nparent, NULL); return 0; }
是的很容易明白他們的意圖。
2.協程切換(switch)
從green_switch->g_switch很是直接,進入協程的核心。協程切換(不須要關注協程運行環境,由於CPU總體,運行時狀態的寄存器進行了切換和保存。)佈局
g_switch(PyGreenlet* target, PyObject* args, PyObject* kwargs) { ......(省略) /* find the real target by ignoring dead greenlets, and if necessary starting a greenlet. */ while (target) { if (PyGreenlet_ACTIVE(target)) { ts_target = target; err = g_switchstack(); break; } if (!PyGreenlet_STARTED(target)) { void* dummymarker; ts_target = target; err = g_initialstub(&dummymarker); if (err == 1) { continue; /* retry the switch */ } break; } target = target->parent; } ................(省略) }
首先就是獲取真正執行的target,固然target分爲兩種狀況,活躍的協程,沒有開始的協程。按照咱們的例子,gr1.switch()這個時候,
是爲開始的協程,下邊執行的是g_initialstub。(g_initialstu作了幾件事情 保存當前協程的環境,將協程的棧空間保存到堆上,這個很簡單,切換回來的時候
把堆上的運行空間複製回來就能夠繼續運行。否則就沒有辦法進行切換)spa
static int GREENLET_NOINLINE(g_initialstub)(void* mark) { ........(省略) //將協程的先後關係處理好,造成一個鏈 if (ts_current->stack_start == NULL) { /* ts_current is dying */ self->stack_prev = ts_current->stack_prev; } else { self->stack_prev = ts_current; } .....(省略) //在g_switchstack.進行 /* perform the initial switch */ err = g_switchstack(); //糾正,這個返回twice是不正確的,返回兩次,只有fork系統調用,由於完成以後,有兩個進程執行。 //這個方法並無返回兩次。由於執行完成以後,就再次進行了切換。不該該認爲返回兩次 /* returns twice! The 1st time with err=1: we are in the new greenlet The 2nd time with err=0: back in the caller's greenlet */ if (err == 1) { /* in the new greenlet */ PyGreenlet* origin; .........(省略) if (args == NULL) { /* pending exception */ result = NULL; } else { //這個方法協程進行了真正的執行。 /* call g.run(*args, **kwargs) */ result = PyEval_CallObjectWithKeywords( run, args, kwargs); Py_DECREF(args); Py_XDECREF(kwargs); } Py_DECREF(run); result = g_handle_exit(result); /* jump back to parent */ self->stack_start = NULL; /* dead */ } // 完成以後,在進行切換 /* jump back to parent */ self->stack_start = NULL; /* dead */ for (parent = self->parent; parent != NULL; parent = parent->parent) { result = g_switch(parent, result, NULL); /* Return here means switch to parent failed, * in which case we throw *current* exception * to the next parent in chain. */ assert(result == NULL); } } }
如今開始關心g_switchstack如何進行切換了。
g_switchstack執行了彙編slp_switch(),在這裏我之查看x86彙編。線程
slp_switch(void) { /*將寄存器內容保存在協程的內存中,當恢復了協程內存以後,將內容恢復到寄存器中。新舊協程不會衝突。 協程切換,也就是將寄存器進行內容進行保存,將棧地址進行保存和恢復*/ int err; void *ebp, *ebx; unsigned short cw; register int *stackref, stsizediff; __asm__ volatile ("" : : : "esi", "edi"); __asm__ volatile ("fstcw %0" : "=m" (cw)); __asm__ volatile ("movl %%ebp, %0" : "=m" (ebp)); __asm__ volatile ("movl %%ebx, %0" : "=m" (ebx)); __asm__ ("movl %%esp, %0" : "=g" (stackref)); { SLP_SAVE_STATE(stackref, stsizediff); __asm__ volatile ( "addl %0, %%esp\n" "addl %0, %%ebp\n" : : "r" (stsizediff) ); SLP_RESTORE_STATE(); __asm__ volatile ("xorl %%eax, %%eax" : "=a" (err)); } __asm__ volatile ("movl %0, %%ebx" : : "m" (ebx)); __asm__ volatile ("movl %0, %%ebp" : : "m" (ebp)); __asm__ volatile ("fldcw %0" : : "m" (cw)); __asm__ volatile ("" : : : "esi", "edi"); return err; }
在彙編代碼中,看到的內容就是保存、恢復寄存器和保存協程狀態,恢復協程狀態。指針
下邊來看如下如何保存狀態的SLP_SAVE_STATE-> slp_save-state
code
static int GREENLET_NOINLINE(slp_save_state)(char* stackref)
{
/* must free all the C stack up to target_stop */
char* target_stop = ts_target->stack_stop;
PyGreenlet* owner = ts_current;
assert(owner->stack_saved == 0);
if (owner->stack_start == NULL)
owner = owner->stack_prev; /* not saved if dying */
else
owner->stack_start = stackref;
while (owner->stack_stop < target_stop) {
//owner的結束地址小於target結束地址,產生了棧覆蓋
/* ts_current is entierely within the area to free */
if (g_save(owner, owner->stack_stop))
return -1; /* XXX */
owner = owner->stack_prev;
}
if (owner != ts_target) {
if (g_save(owner, target_stop))
return -1; /* XXX */
}
return 0;
}
static int g_save(PyGreenlet* g, char* stop) { /* Save more of g's stack into the heap -- at least up to 'stop' g->stack_stop |________| | | |_ _ _ _ stop . . . . . | | ==> . . |________| _______ | | | | | | | | g->stack_start |________| |_______| g->stack_copy intptr_t sz1 = g->stack_saved; intptr_t sz2 = stop - g->stack_start; assert(g->stack_start != NULL); if (sz2 > sz1) { char* c = (char*)PyMem_Realloc(g->stack_copy, sz2); if (!c) { PyErr_NoMemory(); return -1; } memcpy(c+sz1, g->stack_start+sz1, sz2-sz1); g->stack_copy = c; g->stack_saved = sz2; } return 0; }
這就是協程棧的保存,申請內存,而後將棧memcpy進入去。
恢復,也是相似,將保存進內存,拷貝到棧上。而後將寄存器進行修改
不過有點特殊的事情是:python看不到eip(指令指針,執行下一條運行指令),而python對應的內容,在frameobject中,
有PyCodeObject,而執行的時候,在ceval.c能夠看到是經過next_instr來獲取python運行指令。
因此協程有top_frame,就是爲了切換協程時候,對指令集合進行切換
代碼中有使用PyThreadState,可是並無用thread線程,對於PyThreadState的使用只是爲了,方便的讓run_info,這個函數指針,
進行處理成爲各項frameobject,recursion_depth等內容。
注:
1.對於中間的彙編,仍是建議好好的看一下。我恰好看了中國科技大學-孟寧出的一個視頻教程《linux 內核解讀》恰好對彙編有很是好的講解。
2.而協程運行以後,greenlet的棧佈局以下:(圖片來源於網絡)