最近開始研究Python的并行開發(fā)技術(shù),包括多線程,多進(jìn)程,協(xié)程等。逐步整理了網(wǎng)上的一些資料,今天整理了一下greenlet相關(guān)的資料。
并發(fā)處理的技術(shù)背景
并行化處理目前很受重視, 因?yàn)樵诤芏鄷r(shí)候,并行計(jì)算能大大的提高系統(tǒng)吞吐量,尤其在現(xiàn)在多核多處理器的時(shí)代, 所以像lisp這種古老的語言又被人們重新拿了起來, 函數(shù)式編程也越來越流行。 介紹一個(gè)python的并行處理的一個(gè)庫: greenlet。 python 有一個(gè)非常有名的庫叫做 stackless ,用來做并發(fā)處理, 主要是弄了個(gè)叫做tasklet的微線程的東西, 而greenlet 跟stackless的最大區(qū)別是, 他很輕量級(jí)?不夠, 最大的區(qū)別是greenlet需要你自己來處理線程切換, 就是說,你需要自己指定現(xiàn)在執(zhí)行哪個(gè)greenlet再執(zhí)行哪個(gè)greenlet。
greenlet的實(shí)現(xiàn)機(jī)制
以前使用python開發(fā)web程序,一直使用的是fastcgi模式.然后每個(gè)進(jìn)程中啟動(dòng)多個(gè)線程來進(jìn)行請(qǐng)求處理.這里有一個(gè)問題就是需要保證每個(gè)請(qǐng)求響應(yīng)時(shí)間都要特別短,不然只要多請(qǐng)求幾次慢的就會(huì)讓服務(wù)器拒絕服務(wù),因?yàn)闆]有線程能夠響應(yīng)請(qǐng)求了.平時(shí)我們的服務(wù)上線都會(huì)進(jìn)行性能測試的,所以正常情況沒有太大問題.但是不可能所有場景都測試到.一旦出現(xiàn)就會(huì)讓用戶等好久沒有響應(yīng).部分不可用導(dǎo)致全部不可用.后來轉(zhuǎn)換到了coroutine,python 下的greenlet.所以對(duì)它的實(shí)現(xiàn)機(jī)制做了一個(gè)簡單的了解.
每個(gè)greenlet都只是heap中的一個(gè)python object(PyGreenlet).所以對(duì)于一個(gè)進(jìn)程你創(chuàng)建百萬甚至千萬個(gè)greenlet都沒有問題.
代碼如下:
typedef struct _greenlet {
PyObject_HEAD
char* stack_start;
char* stack_stop;
char* stack_copy;
intptr_t stack_saved;
struct _greenlet* stack_prev;
struct _greenlet* parent;
PyObject* run_info;
struct _frame* top_frame;
int recursion_depth;
PyObject* weakreflist;
PyObject* exc_type;
PyObject* exc_value;
PyObject* exc_traceback;
PyObject* dict;
} PyGreenlet;
每一個(gè)greenlet其實(shí)就是一個(gè)函數(shù),以及保存這個(gè)函數(shù)執(zhí)行時(shí)的上下文.對(duì)于函數(shù)來說上下文也就是其stack..同一個(gè)進(jìn)程的所有的greenlets共用一個(gè)共同的操作系統(tǒng)分配的用戶棧.所以同一時(shí)刻只能有棧數(shù)據(jù)不沖突的greenlet使用這個(gè)全局的棧.greenlet是通過stack_stop,stack_start來保存其stack的棧底和棧頂?shù)?如果出現(xiàn)將要執(zhí)行的greenlet的stack_stop和目前棧中的greenlet重疊的情況,就要把這些重疊的greenlet的棧中數(shù)據(jù)臨時(shí)保存到heap中.保存的位置通過stack_copy和stack_saved來記錄,以便恢復(fù)的時(shí)候從heap中拷貝回棧中stack_stop和stack_start的位置.不然就會(huì)出現(xiàn)其棧數(shù)據(jù)會(huì)被破壞的情況.所以應(yīng)用程序創(chuàng)建的這些greenlet就是通過不斷的拷貝數(shù)據(jù)到heap中或者從heap中拷貝到棧中來實(shí)現(xiàn)并發(fā)的.對(duì)于io型的應(yīng)用程序使用coroutine真的非常舒服.
下面是greenlet的一個(gè)簡單的棧空間模型(from greenlet.c)
代碼如下:
A PyGreenlet is a range of C stack addresses that must be
saved and restored in such a way that the full range of the
stack contains valid data when we switch to it.
Stack layout for a greenlet:
| ^^^ |
| older data |
| |
stack_stop . |_______________|
. | |
. | greenlet data |
. | in stack |
. * |_______________| . . _____________ stack_copy + stack_saved
. | | | |
. | data | |greenlet data|
. | unrelated | | saved |
. | to | | in heap |
stack_start . | this | . . |_____________| stack_copy
| greenlet |
| |
| newer data |
| vvv |
下面是一段簡單的greenlet代碼.
代碼如下:
from greenlet import greenlet
def test1():
print 12
gr2.switch()
print 34
def test2():
print 56
gr1.switch()
print 78
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
目前所討論的協(xié)程,一般是編程語言提供支持的。目前我所知提供協(xié)程支持的語言包括python,lua,go,erlang, scala和rust。協(xié)程不同于線程的地方在于協(xié)程不是操作系統(tǒng)進(jìn)行切換,而是由程序員編碼進(jìn)行切換的,也就是說切換是由程序員控制的,這樣就沒有了線程所謂的安全問題。
所有的協(xié)程都共享整個(gè)進(jìn)程的上下文,這樣協(xié)程間的交換也非常方便。
相對(duì)于第二種方案(I/O多路復(fù)用),使得使用協(xié)程寫的程序?qū)⒏拥闹庇^,而不是將一個(gè)完整的流程拆分成多個(gè)管理的事件處理。協(xié)程的缺點(diǎn)可能是無法利用多核優(yōu)勢,不過,這個(gè)可以通過協(xié)程+進(jìn)程的方式來解決。
協(xié)程可以用來處理并發(fā)來提高性能,也可以用來實(shí)現(xiàn)狀態(tài)機(jī)來簡化編程。我用的更多的是第二個(gè)。去年年底接觸python,了解到了python的協(xié)程概念,后來通過pycon china2011接觸到處理yield,greenlet也是一個(gè)協(xié)程方案,而且在我看來是更可用的一個(gè)方案,特別是用來處理狀態(tài)機(jī)。
目前這一塊已經(jīng)基本完成,后面抽時(shí)間總結(jié)一下。
總結(jié)一下:
1)多進(jìn)程能夠利用多核優(yōu)勢,但是進(jìn)程間通信比較麻煩,另外,進(jìn)程數(shù)目的增加會(huì)使性能下降,進(jìn)程切換的成本較高。程序流程復(fù)雜度相對(duì)I/O多路復(fù)用要低。
2)I/O多路復(fù)用是在一個(gè)進(jìn)程內(nèi)部處理多個(gè)邏輯流程,不用進(jìn)行進(jìn)程切換,性能較高,另外流程間共享信息簡單。但是無法利用多核優(yōu)勢,另外,程序流程被事件處理切割成一個(gè)個(gè)小塊,程序比較復(fù)雜,難于理解。
3)線程運(yùn)行在一個(gè)進(jìn)程內(nèi)部,由操作系統(tǒng)調(diào)度,切換成本較低,另外,他們共享進(jìn)程的虛擬地址空間,線程間共享信息簡單。但是線程安全問題導(dǎo)致線程學(xué)習(xí)曲線陡峭,而且易出錯(cuò)。
4)協(xié)程有編程語言提供,由程序員控制進(jìn)行切換,所以沒有線程安全問題,可以用來處理狀態(tài)機(jī),并發(fā)請(qǐng)求等。但是無法利用多核優(yōu)勢。
上面的四種方案可以配合使用,我比較看好的是進(jìn)程+協(xié)程的模式。
更多信息請(qǐng)查看IT技術(shù)專欄