使用Python中的greenlet包實現並發編程的入門教程

來源:互聯網
上載者:User
1 動機

greenlet 包是 Stackless 的副產品,其將微線程稱為 “tasklet” 。tasklet運行在偽並發中,使用channel進行同步資料交換。

一個”greenlet”,是一個更加原始的微線程的概念,但是沒有調度,或者叫做協程。這在你需要控制你的代碼時很有用。你可以自己構造微線程的 調度器;也可以使用”greenlet”實現進階的控制流程。例如可以重新建立構造器;不同於Python的構造器,我們的構造器可以嵌套的調用函數,而被 嵌套的函數也可以 yield 一個值。(另外,你並不需要一個”yield”關鍵字,參考例子)。

Greenlet是作為一個C擴充模組給未修改的解譯器的。

1.1 例子

假設系統是被控制台程式控制的,由使用者輸入命令。假設輸入是一個個字元的。這樣的系統有如如下的樣子:

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)

現在假設你要把程式移植到GUI,而大多數GUI是事件驅動的。他們會在每次的使用者輸入時調用回呼函數。這種情況下,就很難實現 read_next_char() 函數。我們有兩個不相容的函數:

def event_keydown(key):
??

def read_next_char():
?? 需要等待 event_keydown() 的調用

你可能在考慮用線程實現。而 Greenlet 是另一種解決方案,沒有鎖和關閉問題。你啟動 process_commands() 函數,分割成 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_charg_processor=greenlet(process_commands)g_processor.switch(*args)gui.mainloop()

這個例子的執行流程是: read_next_char() 被調用,也就是 g_processor 的一部分,它就會切換(switch)到他的父greenlet,並假設繼續在頂級主迴圈中執行(GUI主迴圈)。當GUI調用 event_keydown() 時,它切換到 g_processor ,這意味著執行會跳回到原來掛起的地方,也就是 read_next_char() 函數中的切換指令那裡。然後 event_keydown() 的 key 參數就會被傳遞到 read_next_char() 的切換處,並返回。

注意 read_next_char() 會被掛起並假設其調用棧會在恢複時保護的很好,所以他會在被調用的地方返回。這允許程式邏輯保持優美的順序流。我們無需重寫 process_commands() 來用到一個狀態機器中。

2 使用

2.1 簡介

一個 “greenlet” 是一個很小的獨立微線程。可以把它想像成一個堆疊框架,棧底是初始調用,而棧頂是當前greenlet的暫停位置。你使用greenlet建立一堆這樣的堆 棧,然後在他們之間跳轉執行。跳轉不是絕對的:一個greenlet必須選擇跳轉到選擇好的另一個greenlet,這會讓前一個掛起,而後一個恢複。兩 個greenlet之間的跳轉稱為 切換(switch) 。

當你建立一個greenlet,它得到一個初始化過的空堆棧;當你第一次切換到它,他會啟動指定的函數,然後切換跳出greenlet。當最終棧底 函數結束時,greenlet的堆棧又編程空的了,而greenlet也就死掉了。greenlet也會因為一個未捕捉的異常死掉。

例如:

from py.magic import greenletdef test1():  print 12  gr2.switch()  print 34def test2():  print 56  gr1.switch()  print 78gr1=greenlet(test1)gr2=greenlet(test2)gr1.switch()

最後一行跳轉到 test1() ,它列印12,然後跳轉到 test2() ,列印56,然後跳回 test1() ,列印34,然後 test1() 就結束,gr1死掉。這時執行會回到原來的 gr1.switch() 調用。注意,78是不會被列印的。

2.2 父greenlet

現在看看一個greenlet死掉時執行點去哪裡。每個greenlet擁有一個父greenlet。父greenlet在每個greenlet初 始化時被建立(不過可以在任何時候改變)。父greenlet是當greenlet死掉時,繼續原來的位置執行。這樣,greenlet就被組織成一棵 樹,頂級的代碼並不在使用者建立的 greenlet 中運行,而稱為主greenlet,也就是樹根。

在上面的例子中,gr1和gr2都是把主greenlet作為父greenlet的。任何一個死掉,執行點都會回到主函數。

未捕獲的異常會波及到父greenlet。如果上面的 test2() 包含一個列印錯誤(typo),他會產生一個 NameError 而幹掉gr2,然後執行點會回到主函數。traceback會顯示 test2() 而不是 test1() 。記住,切換不是調用,但是執行點可以在並行的棧容器間並行交換,而父greenlet定義了棧最初從哪裡來。

2.3 執行個體

py.magic.greenlet 是一個 greenlet 類型,支援如下操作:

greenlet(run=None,parent=None)

建立一個greenlet對象,而不執行。run是執行回調,而parent是父greenlet,預設是當前greenlet。

greenlet.getcurrent()

返回當前greenlet,也就是誰在調用這個函數。

greenlet.GreenletExit

這個特定的異常不會波及到父greenlet,它用於幹掉一個greenlet。

greenlet 類型可以被繼承。一個greenlet通過調用其 run 屬性執行,就是建立時指定的那個。對於子類,可以定義一個 run() 方法,而不必嚴格遵守在構造器中給出 run 參數。

2.4 切換

greenlet之間的切換髮生在greenlet的 switch() 方法被調用時,這會讓執行點跳轉到greenlet的 switch() 被調用處。或者在greenlet死掉時,跳轉到父greenlet那裡去。在切換時,一個對象或異常被發送到目標greenlet。這可以作為兩個greenlet之間傳遞資訊的方便方式。例如:

def test1(x,y):  z=gr2.switch(x+y)  print zdef test2(u):  print u  gr1.switch(42)gr1=greenlet(test1)gr2=greenlet(test2)gr1.switch("hello"," world")

這會列印出 “hello world” 和42,跟前面的例子的輸出順序相同。注意 test1() 和 test2() 的參數並不是在 greenlet 建立時指定的,而是在第一次切換到這裡時傳遞的。

這裡是精確的調用方式:

g.switch(obj=None or *args)

切換到執行點greenlet g,發送給定的對象obj。在特殊情況下,如果g還沒有啟動,就會讓它啟動;這種情況下,會傳遞參數過去,然後調用 g.run(*args) 。

垂死的greenlet

如果一個greenlet的 run() 結束了,他會傳回值到父greenlet。如果 run() 是異常終止的,異常會波及到父greenlet(除非是 greenlet.GreenletExit 異常,這種情況下異常會被捕捉並返回到父greenlet)。

除了上面的情況外,目標greenlet會接收到發送來的對象作為 switch() 的傳回值。雖然 switch() 並不會立即返回,但是它仍然會在未來某一點上返回,當其他greenlet切換回來時。當這發生時,執行點恢複到 switch() 之後,而 switch() 返回剛才調用者發送來的對象。這意味著 x=g.switch(y) 會發送對象y到g,然後等著一個不知道是誰發來的對象,並在這裡返回給x。

注意,任何嘗試切換到死掉的greenlet的行為都會切換到死掉greenlet的父greenlet,或者父的父,等等。最終的父就是 main greenlet,永遠不會死掉的。

2.5 greenlet的方法和屬性

g.switch(obj=None or *args)

切換執行點到greenlet g,同上。

g.run

調用可執行檔g,並啟動。在g啟動後,這個屬性就不再存在了。

g.parent

greenlet的父。這是可寫的,但是不允許建立迴圈的父關係。

g.gr_frame

當前頂級幀,或者None。

g.dead

判斷是否已經死掉了

bool(g)

如果g是活躍的則返回True,在尚未啟動或者結束後返回False。

g.throw([typ,[val,[tb]]])

切換執行點到greenlet g,但是立即拋出指定的異常到g。如果沒有提供參數,異常預設就是 greenlet.GreenletExit 。根據異常波及規則,有如上面描述的。注意調用這個方法等同於如下:

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

2.6 Greenlet與Python線程

greenlet可以與Python線程一起使用;在這種情況下,每個線程包含一個獨立的 main greenlet,並擁有自己的greenlet樹。不同線程之間不可以互相切換greenlet。

2.7 活動greenlet的垃圾收集

如果不再有對greenlet對象的引用時(包括其他greenlet的parent),還是沒有辦法切換回greenlet。這種情況下會產生一個 GreenletExit 異常到greenlet。這是greenlet收到非同步異常的唯一情況。應該給出一個 try .. finally 用於清理greenlet內的資源。這個功能同時允許greenlet中無限迴圈的編程風格。這樣迴圈可以在最後一個引用消失時自動中斷。

如果不希望greenlet死掉或者把引用放到別處,只需要捕捉和忽略 GreenletExit 異常即可。

greenlet不參與垃圾收集;greenlet幀的循環參考資料會被檢測到。將引用傳遞到其他的迴圈greenlet會引起記憶體泄露。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.