使用Python中的greenlet包實(shí)現(xiàn)并發(fā)編程的入門(mén)教程
1 動(dòng)機(jī)
greenlet 包是 Stackless 的副產(chǎn)品,其將微線程稱(chēng)為 “tasklet” 。tasklet運(yùn)行在偽并發(fā)中,使用channel進(jìn)行同步數(shù)據(jù)交換。
一個(gè)”greenlet”,是一個(gè)更加原始的微線程的概念,但是沒(méi)有調(diào)度,或者叫做協(xié)程。這在你需要控制你的代碼時(shí)很有用。你可以自己構(gòu)造微線程的 調(diào)度器;也可以使用”greenlet”實(shí)現(xiàn)高級(jí)的控制流。例如可以重新創(chuàng)建構(gòu)造器;不同于Python的構(gòu)造器,我們的構(gòu)造器可以嵌套的調(diào)用函數(shù),而被 嵌套的函數(shù)也可以 yield 一個(gè)值。(另外,你并不需要一個(gè)”yield”關(guān)鍵字,參考例子)。
Greenlet是作為一個(gè)C擴(kuò)展模塊給未修改的解釋器的。
1.1 例子
假設(shè)系統(tǒng)是被控制臺(tái)程序控制的,由用戶輸入命令。假設(shè)輸入是一個(gè)個(gè)字符的。這樣的系統(tǒng)有如如下的樣子:
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 #忽略指令 process_commands(line)
現(xiàn)在假設(shè)你要把程序移植到GUI,而大多數(shù)GUI是事件驅(qū)動(dòng)的。他們會(huì)在每次的用戶輸入時(shí)調(diào)用回調(diào)函數(shù)。這種情況下,就很難實(shí)現(xiàn) read_next_char() 函數(shù)。我們有兩個(gè)不兼容的函數(shù):
def event_keydown(key):
??
def read_next_char():
?? 需要等待 event_keydown() 的調(diào)用
你可能在考慮用線程實(shí)現(xiàn)。而 Greenlet 是另一種解決方案,沒(méi)有鎖和關(guān)閉問(wèn)題。你啟動(dòng) process_commands() 函數(shù),分割成 greenlet ,然后與按鍵事件交互,有如:
def event_keydown(key): g_processor.switch(key) def read_next_char(): g_self=greenlet.getcurrent() next_char=g_self.parent.switch() #跳到上一層(main)的greenlet,等待下一次按鍵 return next_char g_processor=greenlet(process_commands) g_processor.switch(*args) gui.mainloop()
這個(gè)例子的執(zhí)行流程是: read_next_char() 被調(diào)用,也就是 g_processor 的一部分,它就會(huì)切換(switch)到他的父greenlet,并假設(shè)繼續(xù)在頂級(jí)主循環(huán)中執(zhí)行(GUI主循環(huán))。當(dāng)GUI調(diào)用 event_keydown() 時(shí),它切換到 g_processor ,這意味著執(zhí)行會(huì)跳回到原來(lái)掛起的地方,也就是 read_next_char() 函數(shù)中的切換指令那里。然后 event_keydown() 的 key 參數(shù)就會(huì)被傳遞到 read_next_char() 的切換處,并返回。
注意 read_next_char() 會(huì)被掛起并假設(shè)其調(diào)用棧會(huì)在恢復(fù)時(shí)保護(hù)的很好,所以他會(huì)在被調(diào)用的地方返回。這允許程序邏輯保持優(yōu)美的順序流。我們無(wú)需重寫(xiě) process_commands() 來(lái)用到一個(gè)狀態(tài)機(jī)中。
2 使用
2.1 簡(jiǎn)介
一個(gè) “greenlet” 是一個(gè)很小的獨(dú)立微線程。可以把它想像成一個(gè)堆棧幀,棧底是初始調(diào)用,而棧頂是當(dāng)前greenlet的暫停位置。你使用greenlet創(chuàng)建一堆這樣的堆 棧,然后在他們之間跳轉(zhuǎn)執(zhí)行。跳轉(zhuǎn)不是絕對(duì)的:一個(gè)greenlet必須選擇跳轉(zhuǎn)到選擇好的另一個(gè)greenlet,這會(huì)讓前一個(gè)掛起,而后一個(gè)恢復(fù)。兩 個(gè)greenlet之間的跳轉(zhuǎn)稱(chēng)為 切換(switch) 。
當(dāng)你創(chuàng)建一個(gè)greenlet,它得到一個(gè)初始化過(guò)的空堆棧;當(dāng)你第一次切換到它,他會(huì)啟動(dòng)指定的函數(shù),然后切換跳出greenlet。當(dāng)最終棧底 函數(shù)結(jié)束時(shí),greenlet的堆棧又編程空的了,而greenlet也就死掉了。greenlet也會(huì)因?yàn)橐粋€(gè)未捕捉的異常死掉。
例如:
from py.magic 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() 就結(jié)束,gr1死掉。這時(shí)執(zhí)行會(huì)回到原來(lái)的 gr1.switch() 調(diào)用。注意,78是不會(huì)被打印的。
2.2 父greenlet
現(xiàn)在看看一個(gè)greenlet死掉時(shí)執(zhí)行點(diǎn)去哪里。每個(gè)greenlet擁有一個(gè)父greenlet。父greenlet在每個(gè)greenlet初 始化時(shí)被創(chuàng)建(不過(guò)可以在任何時(shí)候改變)。父greenlet是當(dāng)greenlet死掉時(shí),繼續(xù)原來(lái)的位置執(zhí)行。這樣,greenlet就被組織成一棵 樹(shù),頂級(jí)的代碼并不在用戶創(chuàng)建的 greenlet 中運(yùn)行,而稱(chēng)為主greenlet,也就是樹(shù)根。
在上面的例子中,gr1和gr2都是把主greenlet作為父greenlet的。任何一個(gè)死掉,執(zhí)行點(diǎn)都會(huì)回到主函數(shù)。
未捕獲的異常會(huì)波及到父greenlet。如果上面的 test2() 包含一個(gè)打印錯(cuò)誤(typo),他會(huì)生成一個(gè) NameError 而干掉gr2,然后執(zhí)行點(diǎn)會(huì)回到主函數(shù)。traceback會(huì)顯示 test2() 而不是 test1() 。記住,切換不是調(diào)用,但是執(zhí)行點(diǎn)可以在并行的棧容器間并行交換,而父greenlet定義了棧最初從哪里來(lái)。
2.3 實(shí)例
py.magic.greenlet 是一個(gè) greenlet 類(lèi)型,支持如下操作:
greenlet(run=None,parent=None)
創(chuàng)建一個(gè)greenlet對(duì)象,而不執(zhí)行。run是執(zhí)行回調(diào),而parent是父greenlet,缺省是當(dāng)前greenlet。
greenlet.getcurrent()
返回當(dāng)前greenlet,也就是誰(shuí)在調(diào)用這個(gè)函數(shù)。
greenlet.GreenletExit
這個(gè)特定的異常不會(huì)波及到父greenlet,它用于干掉一個(gè)greenlet。
greenlet 類(lèi)型可以被繼承。一個(gè)greenlet通過(guò)調(diào)用其 run 屬性執(zhí)行,就是創(chuàng)建時(shí)指定的那個(gè)。對(duì)于子類(lèi),可以定義一個(gè) run() 方法,而不必嚴(yán)格遵守在構(gòu)造器中給出 run 參數(shù)。
2.4 切換
greenlet之間的切換發(fā)生在greenlet的 switch() 方法被調(diào)用時(shí),這會(huì)讓執(zhí)行點(diǎn)跳轉(zhuǎn)到greenlet的 switch() 被調(diào)用處。或者在greenlet死掉時(shí),跳轉(zhuǎn)到父greenlet那里去。在切換時(shí),一個(gè)對(duì)象或異常被發(fā)送到目標(biāo)greenlet。這可以作為兩個(gè)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")
這會(huì)打印出 “hello world” 和42,跟前面的例子的輸出順序相同。注意 test1() 和 test2() 的參數(shù)并不是在 greenlet 創(chuàng)建時(shí)指定的,而是在第一次切換到這里時(shí)傳遞的。
這里是精確的調(diào)用方式:
g.switch(obj=None or *args)
切換到執(zhí)行點(diǎn)greenlet g,發(fā)送給定的對(duì)象obj。在特殊情況下,如果g還沒(méi)有啟動(dòng),就會(huì)讓它啟動(dòng);這種情況下,會(huì)傳遞參數(shù)過(guò)去,然后調(diào)用 g.run(*args) 。
垂死的greenlet
如果一個(gè)greenlet的 run() 結(jié)束了,他會(huì)返回值到父greenlet。如果 run() 是異常終止的,異常會(huì)波及到父greenlet(除非是 greenlet.GreenletExit 異常,這種情況下異常會(huì)被捕捉并返回到父greenlet)。
除了上面的情況外,目標(biāo)greenlet會(huì)接收到發(fā)送來(lái)的對(duì)象作為 switch() 的返回值。雖然 switch() 并不會(huì)立即返回,但是它仍然會(huì)在未來(lái)某一點(diǎn)上返回,當(dāng)其他greenlet切換回來(lái)時(shí)。當(dāng)這發(fā)生時(shí),執(zhí)行點(diǎn)恢復(fù)到 switch() 之后,而 switch() 返回剛才調(diào)用者發(fā)送來(lái)的對(duì)象。這意味著 x=g.switch(y) 會(huì)發(fā)送對(duì)象y到g,然后等著一個(gè)不知道是誰(shuí)發(fā)來(lái)的對(duì)象,并在這里返回給x。
注意,任何嘗試切換到死掉的greenlet的行為都會(huì)切換到死掉greenlet的父greenlet,或者父的父,等等。最終的父就是 main greenlet,永遠(yuǎn)不會(huì)死掉的。
2.5 greenlet的方法和屬性
g.switch(obj=None or *args)
切換執(zhí)行點(diǎn)到greenlet g,同上。
g.run
調(diào)用可執(zhí)行的g,并啟動(dòng)。在g啟動(dòng)后,這個(gè)屬性就不再存在了。
g.parent
greenlet的父。這是可寫(xiě)的,但是不允許創(chuàng)建循環(huán)的父關(guān)系。
g.gr_frame
當(dāng)前頂級(jí)幀,或者None。
g.dead
判斷是否已經(jīng)死掉了
bool(g)
如果g是活躍的則返回True,在尚未啟動(dòng)或者結(jié)束后返回False。
g.throw([typ,[val,[tb]]])
切換執(zhí)行點(diǎn)到greenlet g,但是立即拋出指定的異常到g。如果沒(méi)有提供參數(shù),異常缺省就是 greenlet.GreenletExit 。根據(jù)異常波及規(guī)則,有如上面描述的。注意調(diào)用這個(gè)方法等同于如下:
def raiser(): raise typ,val,tb g_raiser=greenlet(raiser,parent=g) g_raiser.switch()
2.6 Greenlet與Python線程
greenlet可以與Python線程一起使用;在這種情況下,每個(gè)線程包含一個(gè)獨(dú)立的 main greenlet,并擁有自己的greenlet樹(shù)。不同線程之間不可以互相切換greenlet。
2.7 活動(dòng)greenlet的垃圾收集
如果不再有對(duì)greenlet對(duì)象的引用時(shí)(包括其他greenlet的parent),還是沒(méi)有辦法切換回greenlet。這種情況下會(huì)生成一個(gè) GreenletExit 異常到greenlet。這是greenlet收到異步異常的唯一情況。應(yīng)該給出一個(gè) try .. finally 用于清理greenlet內(nèi)的資源。這個(gè)功能同時(shí)允許greenlet中無(wú)限循環(huán)的編程風(fēng)格。這樣循環(huán)可以在最后一個(gè)引用消失時(shí)自動(dòng)中斷。
如果不希望greenlet死掉或者把引用放到別處,只需要捕捉和忽略 GreenletExit 異常即可。
greenlet不參與垃圾收集;greenlet幀的循環(huán)引用數(shù)據(jù)會(huì)被檢測(cè)到。將引用傳遞到其他的循環(huán)greenlet會(huì)引起內(nèi)存泄露。
- Python greenlet和gevent使用代碼示例解析
- Python協(xié)程操作之gevent(yield阻塞,greenlet),協(xié)程實(shí)現(xiàn)多任務(wù)(有規(guī)律的交替協(xié)作執(zhí)行)用法詳解
- Python協(xié)程 yield與協(xié)程greenlet簡(jiǎn)單用法示例
- Python greenlet實(shí)現(xiàn)原理和使用示例
- Python中g(shù)event模塊協(xié)程使用
- 簡(jiǎn)單了解python gevent 協(xié)程使用及作用
- 詳解python之協(xié)程gevent模塊
- Python中的協(xié)程(Coroutine)操作模塊(greenlet、gevent)
相關(guān)文章
python實(shí)現(xiàn)自動(dòng)獲取IP并發(fā)送到郵箱
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)自動(dòng)獲取IP并發(fā)到郵箱,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12wxPython電子表格功能wx.grid實(shí)例教程
這篇文章主要介紹了wxPython電子表格功能wx.grid實(shí)例教程,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11Python+OpenCV實(shí)戰(zhàn)之拖拽虛擬方塊的實(shí)現(xiàn)
這篇文章主要介紹了如何利用Python+OpenCV實(shí)現(xiàn)拖拽虛擬方塊的效果,即根據(jù)手指坐標(biāo)位置和矩形的坐標(biāo)位置,判斷手指點(diǎn)是否在矩形上,如果在則矩形跟隨手指移動(dòng),感興趣的可以了解一下2022-08-08Python?通過(guò)colorama?設(shè)置控制臺(tái)、命令行輸出彩色文字
這篇文章主要介紹了Python?通過(guò)colorama?設(shè)置控制臺(tái)、命令行輸出彩色文字的相關(guān)資料,需要的朋友可以參考下2023-09-09Python實(shí)現(xiàn)GUI圖片瀏覽的小程序
這篇文章主要介紹了Python實(shí)現(xiàn)GUI圖片瀏覽程序,程序的實(shí)現(xiàn)需要pillow庫(kù),pillow是 Python 的第三方圖像處理庫(kù),需要安裝才能實(shí)用,文中通過(guò)代碼示例給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12Windows下用py2exe將Python程序打包成exe程序的教程
這篇文章主要介紹了Windows下用py2exe將Python程序打包成exe程序的教程,文中主要針對(duì)Python3.x版本進(jìn)行說(shuō)明,需要的朋友可以參考下2015-04-04python使用參數(shù)對(duì)嵌套字典進(jìn)行取值的方法
這篇文章主要介紹了python使用參數(shù)對(duì)嵌套字典進(jìn)行取值,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-04-04