[譯文]greenlet:輕量級并發(fā)程序

原文:https://greenlet.readthedocs.io/en/latest/

背景

greenlet包是Stackless的衍生產(chǎn)品,它是一個支持微線程(叫tasklets)的CPython版本。Tasklets運行在偽并發(fā)模式下(通常在一個或少許的OS級別的線程)赡模,他們通過“channels”來交互數(shù)據(jù)峦树。

另一方面來說宪哩, 一個“greenlet”任然是一個沒有內(nèi)部調(diào)度的關(guān)于微線程的較為原始的概念褂策。換句話說峭判,當你想要在你代碼運行時做到準確控制笔时,“greenlet”是一種很有用的方式棍好。在greenlet基礎(chǔ)之上,你可以定義自己的微線程調(diào)度策略允耿。不管怎樣借笙,greenlets也可以以一種高級控制流結(jié)構(gòu)的方式用于他們自己。舉個例子较锡,我們可以重新生成迭代器业稼。python自帶的生成器與greenlet的生成器之間的區(qū)別是greenlet的生成器可以嵌套調(diào)用函數(shù),并且嵌套函數(shù)也會yield值(補充說明的是蚂蕴,你不需要使用yield關(guān)鍵詞低散,參見例子:test_generator.py)。

例子

我們來考慮一個用戶輸入命令的終端控制臺系統(tǒng)骡楼。假設(shè)輸入是逐個字符輸入熔号。在這樣的一個系統(tǒng)中,有個典型的循環(huán)如下所示:

def process_commands(*args):
    while True:
        line = ''
        while not line.endswith('\n'):
            line += read_next_char()
        if line == 'quit\n':
            print "are you sure?"
            if read_next_char() != 'y':
                continue    # ignore the command
        process_command(line)

現(xiàn)在君编,假設(shè)你將程序移植到GUI程序中跨嘉,絕大部分的GUI成套工具是基于事件驅(qū)動的。他們?yōu)槊恳粋€用戶字符輸入調(diào)用一個回調(diào)函數(shù)吃嘿。(將“GUI”替換成“XML expat parser”祠乃,對你來說應(yīng)該更加熟悉了)。在這樣的情形中兑燥,執(zhí)行下面的函數(shù)read_next_char()是很困難的亮瓷。這里是兩個不兼容的函數(shù):

def event_keydown(key):
    ??

def read_next_char():
    ?? should wait for the next event_keydown() call

你可能考慮用線程的方式來實現(xiàn)這個了。greenlets是另一種不需要關(guān)聯(lián)鎖與沒有當機問題的可選的解決方案降瞳。你執(zhí)行process_commands()嘱支,獨立的greenlet。通過如下方式輸入字符串挣饥。

def event_keydown(key):
         # jump into g_processor, sending it the key
    g_processor.switch(key)

def read_next_char():
        # g_self is g_processor in this simple example
    g_self = greenlet.getcurrent()
        # jump to the parent (main) greenlet, waiting for the next key
    next_char = g_self.parent.switch()
    return next_char

g_processor = greenlet(process_commands)
g_processor.switch(*args)   # input arguments to process_commands()

gui.mainloop()

這個例子中除师,執(zhí)行流程如下:

  • 當作為g_processor greenlet一部分的read_next_char()函數(shù)被調(diào)用,所以當接收到輸入切換到上級greenlet, 程序恢復(fù)到主循環(huán)(GUI)執(zhí)行扔枫。
  • 當GUI調(diào)用event_keydown()的時候汛聚,程序切換到g_processor。這就意味著程序跳出短荐,無論它被掛起在這個greenlet什么地方倚舀。在這個例子中叹哭,切換到read_next_char(),并且在event_keydown()中被按下的key作為switch()的結(jié)果返回給了read_next_char()。

需要說明的是read_next_char()的掛起與恢復(fù)都保留其調(diào)用堆棧痕貌。以便在prprocess_commands()中根據(jù)他來的地方恢復(fù)到不同的位置风罩。這使得以一種好的控制流來控制程序邏輯成為可能。我們不必完整的重寫process_commands(),將其轉(zhuǎn)換為狀態(tài)機舵稠。

用法

序言

“greenlet” 是微型的獨立的偽線程超升。考慮到作為一個幀堆棧柱查。最遠的幀(最底層)是你調(diào)用的最初的函數(shù)廓俭,最外面的幀(最頂層)是在當前greenlet被壓進去的。當你使用greenlets的時候是通過創(chuàng)建一系列的這種堆棧唉工,然后在他們之間跳轉(zhuǎn)執(zhí)行研乒。這種跳轉(zhuǎn)將會導(dǎo)致先前的幀掛起,最后的幀從掛起狀態(tài)恢復(fù)淋硝。在greenlets之間的跳轉(zhuǎn)關(guān)系叫做“switching(切換)”雹熬。

當你創(chuàng)建一個greenlet,它將有一個初始化的空堆棧。當你第一次切換到它谣膳,它開始運行一個具體的函數(shù)竿报。在這個函數(shù)中可能調(diào)用其他的函數(shù),從當前greenlet中切換出去继谚,等等烈菌。當最底層的函數(shù)完成執(zhí)行,greenlet的棧再次為空花履,這時芽世,greenlet死亡。greenlet也可能應(yīng)一個未捕獲的異常而終止诡壁。

舉個例子:

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()
  • 最后一行跳轉(zhuǎn)到test1, 然后打印12济瓢,
  • 跳轉(zhuǎn)到test2, 然后打印56
  • 跳轉(zhuǎn)回test1, 打印34, test1完成妹卿,并且gr1死亡旺矾。與此同時,程序執(zhí)行返回到gr1.switch()調(diào)用夺克。
  • 需要說明的是78從來都沒有打印箕宙。

父級greenlet

讓我們看看當greenlet死亡的時候,程序執(zhí)行到哪里去了铺纽。每一個greenlet都有一個父級greenlet扒吁。最初的父級是創(chuàng)建greenlet的那一個greenlet(父級greenlet是可以在任何時候被改變)。父級greenlet是當一個greenlet死亡的時候程序繼續(xù)執(zhí)行的地方。這種方式雕崩,程序組織成一顆樹。不在用戶創(chuàng)建的greenlet中運行的頂層代碼在隱式的主greenlet中運行融撞,它是堆棧數(shù)的根盼铁。

在上面的例子中,gr1與gr2將主greenlet作為父級greenlet尝偎。無論它們中的誰執(zhí)行完畢饶火,程序執(zhí)行都會返回到"main"greenlet中。

沒有捕獲的異常將拋出到父級greenlet中致扯。舉個例子肤寝,如果上面的test2()包含一個語法錯誤,它將生成一個殺死gr2的NameError錯誤抖僵,這個錯誤將直接跳轉(zhuǎn)到主greenlet鲤看。錯誤堆棧將顯示test2,而不會是test1。需要注意的是耍群,switches不是調(diào)用义桂,而是程序在并行的"stack container(堆棧容器)"直接執(zhí)行的跳轉(zhuǎn),“parent”定義了邏輯上位于當前greenlet之下的堆棧蹈垢。

實例化對象

greenlet.greenlet是一個協(xié)程類型慷吊,它支持一下操作:

  • greenlet(run=None,parent=None):創(chuàng)建一個新的greenlet對象(還沒有開始運行)。run是一個可調(diào)用的函數(shù)曹抬,用來被調(diào)用溉瓶。parent定義父級greenlet,默認是當前greenlet谤民。
  • greenlet.getcurrent():獲取當前greenlet(即堰酿,調(diào)用該函數(shù)的greenlet)
  • greenlet.GreenletExit:這個特殊的異常不會拋出到父級greenlet中,這可以用來殺死一個單一的greenlet赖临。

greenlet類型可以被子類化胞锰。通過調(diào)用在greenlet創(chuàng)建的時候初始化的run屬性來執(zhí)行一個greenlet。但是對于子類來說兢榨,定義一個run方法比提供一個run參數(shù)給構(gòu)造器更有意義嗅榕。

切換

當在一個greenlet中調(diào)用方法switch(),在greenlet之間的切換將發(fā)生吵聪,正常情況下凌那,程序執(zhí)行跳轉(zhuǎn)到switch()被調(diào)用的greenlet中∫魇牛或者當一個greenlet死亡帽蝶,程序執(zhí)行將跳轉(zhuǎn)到父級greenlet程序中,當發(fā)生切換的時候块攒,一個對象或一個異常被發(fā)送到目標greenlet中励稳。這是一種在兩個greenlet中傳遞信息的便利的方式佃乘。舉個例子:

def test1(x, y):
    z = gr2.switch(x+y)
    print z

def test2(u):
    print u
    gr1.switch(42)

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch("hello", " world")

已與之前例子相同順序執(zhí)行,它將會打印“hello world”與42驹尼。多說一句趣避,test1(),test2()的參數(shù)不是在greenlet創(chuàng)建的時候給的,而是在第一次切換的時候給出新翎。

這里給出了關(guān)于發(fā)送的數(shù)據(jù)的明確的規(guī)則:

g.switch(*args, **kwargs):切換執(zhí)行到greenlet g,發(fā)送數(shù)據(jù)程帕,作為一個特殊的例子,如果g沒有執(zhí)行地啰,它將開始執(zhí)行愁拭。

對于將死的greenlet。當run()完成的時候亏吝,將會發(fā)生對象給父級greenlet岭埠。如果greenlet因為異常而終止,這個異常將會拋出到父級greenlet中(greenlet.GreenletExit例外顺呕,這個異常被捕獲了并且直接退出到父級greenlet中)枫攀。

除了上面例子描述的,通常目標greenlet(父級greenlet)接收之前調(diào)用switch()掛起株茶,執(zhí)行完畢返回的返回值作為結(jié)果来涨。事實上,雖然對switch()的調(diào)用不會立即返回結(jié)果启盛,但是當其他一些greenlet切換回來的時候蹦掐,在將來的某個點將會返回結(jié)果。當切換發(fā)生的時候僵闯,程序?qū)⒃谒皰炱鸬牡胤交謴?fù)卧抗。switch()自己返回發(fā)生的對象。這就意味著x=g.switch(y)yg,稍后將返回從某個不關(guān)聯(lián)的greenlet中返回的不關(guān)聯(lián)的對象給x變量鳖粟。

提醒一下社裆,任何試圖切換到一個死亡的greenlet的將會走到死亡greenlet的父級,或者父級的父級向图,以此類推(最終的父級是“main” greenlet,它是從來不會死掉的)泳秀。

greenlets的方法與屬性

  • g.switch(*args, **kwargs):切換程序到greenlet g中執(zhí)行,參見上面榄攀。

  • g.run:當它開始的時候嗜傅,g的回調(diào)將會被執(zhí)行,當g已經(jīng)開始執(zhí)行了檩赢,這個屬性將不會存在了吕嘀。

  • g.parent:父級greenlet。這是可編輯屬性,但是不能夠?qū)懗闪怂姥h(huán)偶房。

  • g.gr_frame:最頂層的結(jié)構(gòu)趁曼,或者等于None。

  • g.dead: bool值棕洋,當g死亡了彰阴,值為True。

  • bool(g):bool值拍冠,當返回結(jié)構(gòu)是True,表示g還活躍簇抵,如果是False庆杜,表示它死亡了或者還沒開始。

  • g.throw([typ, [val, [tb]]]):切換到g執(zhí)行碟摆,但是立馬拋出一個給定的異常晃财。如果沒有參數(shù)提供,默認異常是greenlet.GreenletExit典蜕。同上面描述一樣断盛,正常的異常傳遞規(guī)則生效。調(diào)用該方法同下面代碼是幾乎等價的:

    def raiser():
        raise typ, val, tb
    g_raiser = greenlet(raiser, parent=g)
    g_raiser.switch()
    

    有一點不同的是愉舔,這段代碼不能用于greenlet.GreenletExit異常钢猛,這個異常將不會從g_raiser傳播到g赌躺。

Greenlets與python的線程

Greenlets將可以和python線程結(jié)合起來撤卢。這種情況下阱表,每一個線程包含一個獨立的帶有一個子greenlets樹的“main” greenlet吉捶〉簦混合或切換在不同線程中的greenlets是不可能的事情恭陡。

greenlets的垃圾回收生命周期

如果對一個greenlet的所有關(guān)聯(lián)都已經(jīng)失效(包括來自其他greenlets中的父級屬性的關(guān)聯(lián))对雪,這時候乔夯,沒有任何一種方式切換回該greenlet中馏鹤。這種情況下征椒,GreenletExit異常將會產(chǎn)生。這是一個greenlet接受異步執(zhí)行的唯一方式湃累。使用try:finally:語句塊來清理被這個greenlet使用的資源勃救。這種屬性支持一種編程風(fēng)格,greenlet無限循環(huán)等待數(shù)據(jù)并且執(zhí)行脱茉。當對該greenlet的最后關(guān)聯(lián)失效剪芥,這種循環(huán)將自動終止。

如果greenlet要么死亡琴许,要么根據(jù)存在某個地方的關(guān)聯(lián)恢復(fù)税肪。只需要捕獲與忽略可能導(dǎo)致無限循環(huán)的GreenletExit。

Greenlets不參與垃圾回收。循環(huán)那些在greenlet框架中的數(shù)據(jù)時候益兄,這些數(shù)據(jù)將不會被檢測到锻梳。循環(huán)的存儲其他greenlets的引用將可能導(dǎo)致內(nèi)存泄漏。

錯誤堆棧支持

當使用greenlet的時候净捅,標準的python錯誤堆棧與描述將不會按照預(yù)期的運行疑枯,因為堆棧與框架的切換發(fā)生在相同的線程中。使用傳統(tǒng)的方法可靠的檢測greenlet切換是一件很困難的事情蛔六。因此荆永,為了改善對greenlet基礎(chǔ)代碼的調(diào)試,錯誤堆棧国章,問題描述的支持具钥,在greenlet模塊中,有一些新的方法:

  • greenlet.gettrace():返回先前已有的調(diào)用堆棧方法液兽,或者None骂删。

  • greenlet.settrace(callback):設(shè)置一個新的調(diào)用堆棧方法,返回前期已有的方法或者None四啰。當某些事件發(fā)生時宁玫,這個回調(diào)函數(shù)被調(diào)用,可以永安里做一下信號處理柑晒。

    def callback(event, args):
        if event == 'switch':
            origin, target = args
            # Handle a switch from origin to target.
            # Note that callback is running in the context of target
            # greenlet and any exceptions will be passed as if
            # target.throw() was used instead of a switch.
            return
        if event == 'throw':
            origin, target = args
            # Handle a throw from origin to target.
            # Note that callback is running in the context of target
            # greenlet and any exceptions will replace the original, as
            # if target.throw() was used with the replacing exception.
            return
    

    為了兼容欧瘪,當事件要么是switch要么是throw,而不是其他可能的事件時候,將參數(shù)解包成tuple敦迄。這樣恋追,API可能擴展出于sys.settrace()相似的新的事件。

C API 相關(guān)

Greenlets可以通過用C/C++寫的擴展模塊來生成與維護罚屋,或者來自于嵌入到python中的應(yīng)用苦囱。greenlet.h 頭文件被提供,用來展示對原生的python模塊的完整的API訪問脾猛。

類型

Type name Python name
PyGreenlet greenlet.greenlet

異常

Type name Python name
PyExc_GreenletError greenlet.error
PyExc_GreenletExit greenlet.GreenletExit

關(guān)聯(lián)

  • PyGreenlet_Import():一個宏定義撕彤,導(dǎo)入greenlet模塊,初始化C API猛拴。必須在每一個用到greenlet C API的模塊中調(diào)用一次羹铅。
  • int PyGreenlet_Check(PyObject *p):一個宏定義,如果參數(shù)是PyGreenlet返回true愉昆。
  • int PyGreenlet_STARTED(PyGreenlet *g):一個宏定義职员,如果greenlet在開始了返回true。
  • int PyGreenlet_ACTIVE(PyGreenlet *g):一個宏定義跛溉,如果greenlet在活動中返回true焊切。
  • PyGreenlet *PyGreenlet_GET_PARENT(PyGreenlet *g):一個宏定義扮授,返回greenlet中的父級greenlet。
  • int PyGreenlet_SetParent(PyGreenlet *g, PyGreenlet *nparent):設(shè)置父級greenlet专肪。返回0為設(shè)置成功刹勃,-1,表示g不是一有效的PyGreenlet指針,AttributeError將拋出嚎尤。
  • PyGreenlet *PyGreenlet_GetCurrent(void):返回當前活躍的greenlet對象荔仁。
  • PyGreenlet *PyGreenlet_New(PyObject *run, PyObject *parent):使用runparent創(chuàng)建一個新的greenlet對象。這兩個參數(shù)是可選的芽死。如果run是NULL乏梁。這個greenlet創(chuàng)建,如果切換開始將失敗关贵。如果parent是NULL掌呜。這個parent將自動設(shè)置成當前greenlet。
  • PyObject *PyGreenlet_Switch(PyGreenlet *g, PyObject *args, PyObject *kwargs):切換到greenet g坪哄。argskwargs是可選的,可以為NULL势篡。如果args為NULL,一個空的tuple將發(fā)送給目標greenlet g翩肌。如果kwargs是NULL的。沒有key-value參數(shù)發(fā)送禁悠。如果指定參數(shù)念祭,那么args應(yīng)該是一個tuple,kwargs應(yīng)該是一個dict。
  • PyObject *PyGreenlet_Throw(PyGreenlet *g, PyObject *typ, PyObject *val, PyObject *tb):切換到greenlet g,并且立馬拋出typ參數(shù)(攜帶的值val)指定的異常碍侦,調(diào)用堆棧對象tb是可選的粱坤,并且可以為NULL。

索引與表

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瓷产,一起剝皮案震驚了整個濱河市站玄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌濒旦,老刑警劉巖株旷,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異尔邓,居然都是意外死亡晾剖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門梯嗽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來齿尽,“玉大人,你說我怎么就攤上這事灯节⊙罚” “怎么了绵估?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贷岸。 經(jīng)常有香客問我壹士,道長,這世上最難降的妖魔是什么偿警? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任躏救,我火速辦了婚禮,結(jié)果婚禮上螟蒸,老公的妹妹穿的比我還像新娘盒使。我一直安慰自己,他們只是感情好七嫌,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布少办。 她就那樣靜靜地躺著,像睡著了一般诵原。 火紅的嫁衣襯著肌膚如雪英妓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天绍赛,我揣著相機與錄音蔓纠,去河邊找鬼。 笑死吗蚌,一個胖子當著我的面吹牛腿倚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蚯妇,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼敷燎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了箩言?” 一聲冷哼從身側(cè)響起硬贯,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎陨收,沒想到半個月后澄成,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡畏吓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年墨状,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菲饼。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡肾砂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宏悦,到底是詐尸還是另有隱情镐确,我是刑警寧澤包吝,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站源葫,受9級特大地震影響诗越,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜息堂,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一嚷狞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荣堰,春花似錦床未、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至渡八,卻和暖如春啃洋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屎鳍。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工裂允, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哥艇。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像僻澎,于是被迫代替她去往敵國和親貌踏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理窟勃,服務(wù)發(fā)現(xiàn)祖乳,斷路器,智...
    卡卡羅2017閱讀 134,629評論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法秉氧,類相關(guān)的語法眷昆,內(nèi)部類的語法,繼承相關(guān)的語法汁咏,異常的語法亚斋,線程的語...
    子非魚_t_閱讀 31,598評論 18 399
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,130評論 30 470
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,755評論 25 707
  • 想找個圖片做昵稱閱讀 201評論 0 0