99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

用greenlet實現(xiàn)Python中的并發(fā)

發(fā)布時間:2017-01-04 22:49  回復(fù):0  查看:2330   最后回復(fù):2017-01-04 22:49  

python開發(fā)中,協(xié)程(Coroutine,也被稱為微線程可以在一個函數(shù)執(zhí)行過程中將其掛起,去執(zhí)行另一個函數(shù),并在必要時將之前的函數(shù)喚醒。在Python的語言環(huán)境里,協(xié)程是相當(dāng)常用的實現(xiàn)并發(fā)的方法。這里我們要介紹一個非常好用的框架greenlet,很多知名的網(wǎng)絡(luò)并發(fā)框架如eventlet,gevent都是基于它實現(xiàn)的。

  第一個例子

  沿襲我們一直以來的習(xí)慣,先從例子開始,這次偷個懶,直接把 官方文檔 中的例子拿過來:

  from greenletimport 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()

  這里創(chuàng)建了兩個greenlet協(xié)程對象,gr1gr2,分別對應(yīng)于函數(shù)test1()test2()。使用greenlet對象的switch()方法,即可以切換協(xié)程。上例中,我們先調(diào)用”gr1.switch()”,函數(shù)test1()被執(zhí)行,然后打印出”12″;接著由于”gr2.switch()”被調(diào)用,協(xié)程切換到函數(shù)test2(),打印出”56″;之后”gr1.switch()”又被調(diào)用,所以又切換到函數(shù)test1()。但注意,由于之前test1()已經(jīng)執(zhí)行到第5行,也就是”gr2.switch()”,所以切換回來后會繼續(xù)往下執(zhí)行,也就是打印”34″;現(xiàn)在函數(shù)test1()退出,同時程序退出。由于再沒有”gr2.switch()”來切換至函數(shù)test2(),所以程序第11”print 78″不會被執(zhí)行。

  所以,程序運行下來的輸出就是:

  很好理解吧。使用switch()方法切換協(xié)程,也比”yield”, “next/send”組合要直觀的多。上例中,我們也可以看出,greenlet協(xié)程的運行,其本質(zhì)是串行的,所以它不是真正意義上的并發(fā),因此也無法發(fā)揮CPU多核的優(yōu)勢,不過,這個可以通過協(xié)程+進程組合的方式來解決,本文就不展開了。另外要注意的是,在沒有進行顯式切換時,部分代碼是無法被執(zhí)行到的,比如上例中的”print 78″。

  父子關(guān)系

  創(chuàng)建協(xié)程對象的方法其實有兩個參數(shù)”greenlet(run=None, parent=None)”。參數(shù)”run”就是其要調(diào)用的方法,比如上例中的函數(shù)test1()test2();參數(shù)”parent”定義了該協(xié)程對象的父協(xié)程,也就是說,greenlet協(xié)程之間是可以有父子關(guān)系的。如果不設(shè)或設(shè)為空,則其父協(xié)程就是程序默認的”main”主協(xié)程。這個”main”協(xié)程不需要用戶創(chuàng)建,它所對應(yīng)的方法就是主程序,而所有用戶創(chuàng)建的協(xié)程都是其子孫。大家可以把greenlet協(xié)程集看作一顆樹,樹的根節(jié)點就是”main”,上例中的”gr1″”gr2″就是其兩個字節(jié)點。

  在子協(xié)程執(zhí)行完畢后,會自動返回父協(xié)程。比如上例中test1()函數(shù)退出,代碼會返回到主程序。讓我們寫個更清晰的例子來實驗下:

  from greenletimport greenlet

  def test1():

  print 12

  gr2.switch()

  print 34

  def test2():

  print 56

  gr1 = greenlet(test1)

  gr2 = greenlet(test2, gr1)

  gr1.switch()print 78

  這里創(chuàng)建greenlet對象”gr2″時,指定了其父協(xié)程是”gr1″。所以在函數(shù)test2()里,雖然沒有”gr1.switch()”代碼,但是在其退出后,程序一樣回到了函數(shù)test1(),并且執(zhí)行”print 34″。同樣,在test1()退出后,代碼回到了主程序,并執(zhí)行”print 78″。所以,最后的輸出就是:

  如果上例中,”gr2″的父協(xié)程不是”gr1″而是”main”的話,那test2()運行完畢就會回到主程序并直接打印”78″,這樣”print 34″就不會執(zhí)行。大家可以試一試。

  還有一個重要的點,就是協(xié)程退出后,就無法再被執(zhí)行了。如果上例在函數(shù)test1()中,再加一句”gr2.switch()”,運行的結(jié)果是一樣的。因為第二次調(diào)用”gr2.switch()”,什么也不會運行。

  def test1():

  print 12

  gr2.switch()

  print 34

  gr2.switch()

  大家可能會感覺到父子協(xié)程之間的關(guān)系,就像函數(shù)調(diào)用一樣,一個嵌套一個。的確,其實greenlet協(xié)程的實現(xiàn)就是使用了棧,其運行的上下文保存在棧中,”main”主協(xié)程處于棧底的位置,而當(dāng)前運行中的協(xié)程就在棧頂。這同函數(shù)是一樣。此外,在任何時候,你都可以使用”greenlet.getcurrent()”,獲取當(dāng)前運行中的協(xié)程對象。比如在函數(shù)test2()中執(zhí)行”greenlet.getcurrent()”,其返回就等于”gr2″

  異常

  既然協(xié)程是存放在棧中,那一個協(xié)程要拋出異常,就會先拋到其父協(xié)程中,如果所有父協(xié)程都不捕獲此異常,程序才會退出。我們試下,把上面的例子中函數(shù)test2()的代碼改為:

  def test2():

  print 56

  raise NameError

  程序執(zhí)行后,我們可以看到Traceback信息:

  File "parent.py", line 14, in

  gr1.switch()

  File "parent.py", line 5, in test1

  gr2.switch()

  File "parent.py", line 10, in test2

  raiseNameError

  同時大家可以試下,如果將”gr2″的父協(xié)程設(shè)為空,Traceback信息就會變?yōu)椋?/span>

  File "parent.py", line 14, in

  gr1.switch()

  File "parent.py", line 10, in test2

  raiseNameError

  因此,如果”gr2″的父協(xié)程是”gr1″的話,異常先回拋到函數(shù)test1()的代碼”gr2.switch()”處。所以,我們再對函數(shù)test1()改動下:

  def test1():

  print 12

  try:

  gr2.switch()

  except NameError:

  print 90

  print 34

  運行后的結(jié)果,如果”gr2″的父協(xié)程是”gr1″,則異常被捕獲,并打印90。否則,異常會被拋出。以上實驗很好的證明了,子協(xié)程拋出的異常會根據(jù)棧里的順序,依次拋到父協(xié)程里。

  有一個異常是特例,不會被拋到父協(xié)程中,那就是”greenlet.GreenletExit”,這個異常會讓當(dāng)前協(xié)程強制退出。比如,我們將函數(shù)test2()改為:

  def test2():

  print 56

  raise greenlet.GreenletExit

  print 78

  那代碼行”print 78″永遠不會被執(zhí)行。但這個異常不會往上拋,所以其父協(xié)程還是可以正常運行。

  另外,我們可以通過greenlet對象的”throw()”方法,手動往一個協(xié)程里拋個異常。比如,我們在test1()里調(diào)一個throw()方法:

  def test1():

  print 12

  gr2.throw(NameError)

  try:

  gr2.switch()

  except NameError:

  print 90

  print 34

  這樣,異常就會被拋出,運行后的Trackback是這樣的:

  File "exception.py", line 21, in

  gr1.switch()

  File "exception.py", line 5, in test1

  gr2.throw(NameError)

  如果將”gr2.throw(NameError)”放在”try”語句中,那該異常就會被捕獲,并打印”90″。另外,當(dāng)”gr2″的父協(xié)程不是”gr1″而是”main”時,異常會直接拋到主程序中,此時函數(shù)test1()中的”try”語句就不起作用了。

  協(xié)程間傳遞消息

  在介紹生成器時,我們聊過可以使用生成器的send()方法來傳遞參數(shù)。greenlet也同樣支持,只要在其switch()方法調(diào)用時,傳入?yún)?shù)即可。我們再來基于本文第一個例子改造下:

  from greenletimport greenlet

  def test1():

  print 12

  y = gr2.switch(56)

  print y

  def test2(x):

  print x

  gr1.switch(34)

  print 78

  gr1 = greenlet(test1)

  gr2 = greenlet(test2)

  gr1.switch()

  在test1()中調(diào)用”gr2.switch()”,由于協(xié)程”gr2″之前未被啟動,所以傳入的參數(shù)”56″會被賦在test2()函數(shù)的參數(shù)”x”上;在test2()中調(diào)用”gr1.switch()”,由于協(xié)程”gr1″之前已執(zhí)行到第5”y = gr2.switch(56)”這里,所以傳入的參數(shù)”34″會作為”gr2.switch(56)”的返回值,賦給變量”y”。這樣,兩個協(xié)程之間的互傳消息就實現(xiàn)了。

  讓我們將上一篇介紹生成器時寫的生產(chǎn)者消費者的例子,改為greenlet實現(xiàn)吧:

  from greenletimport greenlet

  def consumer():

  last = ''

  while True:

  receival = pro.switch(last)

  if receivalis not None:

  print 'Consume %s' % receival

  last = receival

  def producer(n):

  con.switch()

  x = 0

  while x < n:

  x += 1

  print 'Produce %s' % x

  last = con.switch(x)

  pro = greenlet(producer)

  con = greenlet(consumer)

  pro.switch(5)

 

來源:Python-伯樂在線

您還未登錄,請先登錄

熱門帖子

最新帖子

?