實作協程主要是在協程切換時,将協程目前的執行上下文儲存到協程關聯的context中。在c/c++這種native程式中實作協程,需要将棧内容和CPU各個寄存器的内容儲存起來。在Python這種VM中則有些不同。例如,在以下基于greenlet協程的python程式中:
在<code>bar</code>中<code>gr2.switch</code>切換到gr2時,協程庫需要儲存gr1協程的執行上下文。這個上下文包括:
Python VM的stack
Python VM中解釋執行的上下文
了解以上兩點非常重要,至于為什麼呢?想象一下如何去實作一個Python
VM在執行代碼時,其自身調用棧通常都是遞歸的
VM在執行代碼時,通常會建立相應的資料結構來表示代碼執行塊,例如通常會有個<code>struct Frame</code>來表示一個函數
在VM的實作中通常會有類似以下的代碼:
對應到前面的Python例子代碼,在某一時刻VM的call stack可能是這樣的:
了解了以上内容後,就可以推測出greenlet本質上也是做了以上兩件事。
greenlet庫中每一個協程稱為一個greenlet。greenlet都有一個棧空間,如下圖:

圖中未表達出來的,greenlet的棧空間位址可能是重疊的。對于活躍的(目前正在運作)的greenlet,其棧内容必然在c程式棧頂。而不活躍的被切走的greenlet,其棧内容會被copy到新配置設定的堆記憶體中。greenlet的棧空間是動态的,其起始位址是固定的,但棧頂位址不固定。以下代碼展示一個greenlet的棧空間如何确定:
以上<code>greenlet->stack_stop</code>确定了棧底,而棧頂則是動态的,在切換到其他greenlet前,對目前greenlet進行上下文的儲存時,擷取目前的RSP(程式實際運作的棧頂位址):
<code>stackref</code>是通過彙編擷取目前RSP寄存器的值:
儲存棧内容到堆記憶體參看<code>g_save</code>的實作,沒什麼特别的。除了儲存棧内容外,如上一節講的,還需要儲存VM執行函數所對應的<code>Frame</code>對象,這個在<code>g_switchstack</code>中展現:
上面的代碼展示VM frame的切換。接下來看下最複雜的部分,當切換到目标greenlet時,如何恢複目标greenlet的執行上下文,這裡主要就是恢複目标greenlet的棧空間。假設有如下greenlet應用代碼:
在gr1中切換到gr2時,也就是<code>gr2.switch</code>,會發生什麼事情。
以上,首先将ESP/EBP的值改回目标greenlet當初切換走時的ESP/EBP值,然後再把greenlet的棧空間記憶體(存放于堆記憶體中)全部複制回來,就實作了greenlet棧的回切。尤其注意的是,這個棧中是儲存了各種函數的return位址的,是以當<code>slp_switch</code>傳回時,就完全恢複到了目标greenlet當初被切走時棧上的内容,包括各種函數調用棧。而目前greenlet的棧,則停留在了類似以下的函數調用棧:
<a href="https://www.shymonk.com/posts/2016/06/stackless-python-tan-mi/">Stackless Python 探秘</a>
<a href="http://blog.csdn.net/permike/article/details/54846675">python協程的實作(greenlet源碼分析)</a>
<a href="http://blog.csdn.net/yueguanghaidao/article/details/24281751">深度分析gevent運作流程</a>