greenlet 详解
greenlet初體驗(yàn)
回到頂部
Greenlet是python的一個(gè)C擴(kuò)展,來源于Stackless python,旨在提供可自行調(diào)度的‘微線程’, 即協(xié)程。generator實(shí)現(xiàn)的協(xié)程在yield value時(shí)只能將value返回給調(diào)用者(caller)。 而在greenlet中,target.switch(value)可以切換到指定的協(xié)程(target), 然后yield value。greenlet用switch來表示協(xié)程的切換,從一個(gè)協(xié)程切換到另一個(gè)協(xié)程需要顯式指定。
greenlet的安裝很簡(jiǎn)單:pip install greenlet 即可,安裝好了之后我們來看一個(gè)官方的例子
?1?from?greenlet?import?greenlet?2?def?test1():?3?????print?12?4?????gr2.switch()?5?????print?34?6??7?def?test2():?8?????print?56?9?????gr1.switch()10?????print?7811?12?gr1?=?greenlet(test1)13?gr2?=?greenlet(test2)14?gr1.switch() 輸出為:
12 56 34
當(dāng)創(chuàng)建一個(gè)greenlet時(shí),首先初始化一個(gè)空的棧, switch到這個(gè)棧的時(shí)候,會(huì)運(yùn)行在greenlet構(gòu)造時(shí)傳入的函數(shù)(首先在test1中打印 12), 如果在這個(gè)函數(shù)(test1)中switch到其他協(xié)程(到了test2 打印34),那么該協(xié)程會(huì)被掛起,等到切換回來(在test2中切換回來 打印34)。當(dāng)這個(gè)協(xié)程對(duì)應(yīng)函數(shù)執(zhí)行完畢,那么這個(gè)協(xié)程就變成dead狀態(tài)。
注意?上面沒有打印test2的最后一行輸出 78,因?yàn)樵趖est2中切換到gr1之后掛起,但是沒有地方再切換回來。這個(gè)可能造成泄漏,后面細(xì)說。
greenlet module與class
回到頂部
我們首先看一下greenlet這個(gè)module里面的屬性
>>> dir(greenlet)
['GREENLET_USE_GC', 'GREENLET_USE_TRACING', 'GreenletExit', '_C_API', '__doc__', '__file__', '__name__', '__package__', '__version__', 'error', 'getcurrent', 'gettrace', 'greenlet', 'settrace']
>>>
其中,比較重要的是getcurrent(), 類greenlet、異常類GreenletExit。
getcurrent()返回當(dāng)前的greenlet實(shí)例;
GreenletExit:是一個(gè)特殊的異常,當(dāng)觸發(fā)了這個(gè)異常的時(shí)候,即使不處理,也不會(huì)拋到其parent(后面會(huì)提到協(xié)程中對(duì)返回值或者異常的處理)
然后我們?cè)賮砜纯磄reenlet.greenlet這個(gè)類:
>>> dir(greenlet.greenlet)
['GreenletExit', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getstate__', '__hash__', '__init__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_stack_saved', 'dead', 'error', 'getcurrent', 'gettrace', 'gr_frame', 'parent', 'run', 'settrace','switch', 'throw']
>>>?
比較重要的幾個(gè)屬性:
run:當(dāng)greenlet啟動(dòng)的時(shí)候會(huì)調(diào)用到這個(gè)callable,如果我們需要繼承g(shù)reenlet.greenlet時(shí),需要重寫該方法
switch:前面已經(jīng)介紹過了,在greenlet之間切換
parent:可讀寫屬性,后面介紹
dead:如果greenlet執(zhí)行結(jié)束,那么該屬性為true
throw:切換到指定greenlet后立即跑出異常
文章后面提到的greenlet大多都是指greenlet.greenlet這個(gè)class,請(qǐng)注意區(qū)別?
Switch not call
回到頂部
對(duì)于greenlet,最常用的寫法是 x = gr.switch(y)。 這句話的意思是切換到gr,傳入?yún)?shù)y。當(dāng)從其他協(xié)程(不一定是這個(gè)gr)切換回來的時(shí)候,將值付給x。
?1?import?greenlet?2?def?test1(x,?y):?3?????z?=?gr2.switch(x+y)?4?????print('test1?',?z)?5??6?def?test2(u):?7?????print('test2?',?u)?8?????gr1.switch(10)?9?10?gr1?=?greenlet.greenlet(test1)11?gr2?=?greenlet.greenlet(test2)12?print?gr1.switch("hello",?"?world")?
輸出:
('test2 ', 'hello world')
('test1 ', 10)
None
上面的例子,第12行從main greenlet切換到了gr1,test1第3行切換到了gs2,然后gr1掛起,第8行從gr2切回gr1時(shí),將值(10)返回值給了 z。?
?
每一個(gè)Greenlet都有一個(gè)parent,一個(gè)新的greenlet在哪里創(chuàng)生,當(dāng)前環(huán)境的greenlet就是這個(gè)新greenlet的parent。所有的greenlet構(gòu)成一棵樹,其跟節(jié)點(diǎn)就是還沒有手動(dòng)創(chuàng)建greenlet時(shí)候的”main” greenlet(事實(shí)上,在首次import greenlet的時(shí)候?qū)嵗?#xff09;。當(dāng)一個(gè)協(xié)程 正常結(jié)束,執(zhí)行流程回到其對(duì)應(yīng)的parent;或者在一個(gè)協(xié)程中拋出未被捕獲的異常,該異常也是傳遞到其parent。學(xué)習(xí)python的時(shí)候,有一句話會(huì)被無數(shù)次重復(fù)”everything is oblect”, 在學(xué)習(xí)greenlet的調(diào)用中,同樣有一句話應(yīng)該深刻理解, “switch not call”。
?1?import?greenlet?2?def?test1(x,?y):?3?????print?id(greenlet.getcurrent()),?id(greenlet.getcurrent().parent)?#?40240272?40239952?4?????z?=?gr2.switch(x+y)?5?????print?'back?z',?z?6??7?def?test2(u):?8?????print?id(greenlet.getcurrent()),?id(greenlet.getcurrent().parent)?#?40240352?40239952?9?????return?'hehe'10?11?gr1?=?greenlet.greenlet(test1)12?gr2?=?greenlet.greenlet(test2)13?print?id(greenlet.getcurrent()),?id(gr1),?id(gr2)?????#?40239952,?40240272,?4024035214?print?gr1.switch("hello",?"?world"),?'back?to?main'????#?hehe?back?to?main?
上述例子可以看到,盡量是從test1所在的協(xié)程gr1 切換到了gr2,但gr2的parent還是’main’ greenlet,因?yàn)槟J(rèn)的parent取決于greenlet的創(chuàng)生環(huán)境。另外 在test2中return之后整個(gè)返回值返回到了其parent,而不是switch到該協(xié)程的地方(即不是test1),這個(gè)跟我們平時(shí)的函數(shù)調(diào)用不一樣,記住“switch not call”。對(duì)于異常 也是展開至parent
mport?greenletdef?test1(x,?y):????try:z?=?gr2.switch(x+y)????except?Exception:????????print?'catch?Exception?in?test1'def?test2(u):????assert?Falsegr1?=?greenlet.greenlet(test1) gr2?=?greenlet.greenlet(test2)try:gr1.switch("hello",?"?world")except:????print?'catch?Exception?in?main'輸出為:
catch Exception in main
?
Greenlet生命周期
回到頂部
文章開始的地方提到第一個(gè)例子中的gr2其實(shí)并沒有正常結(jié)束,我們可以借用greenlet.dead這個(gè)屬性來查看
?1?from?greenlet?import?greenlet?2?def?test1():?3?????gr2.switch(1)?4?????print?'test1?finished'?5??6?def?test2(x):?7?????print?'test2?first',?x?8?????z?=?gr1.switch()?9?????print?'test2?back',?z10?11?gr1?=?greenlet(test1)12?gr2?=?greenlet(test2)13?gr1.switch()14?print?'gr1?is?dead?:?%s,?gr2?is?dead?:?%s'?%?(gr1.dead,?gr2.dead)15?gr2.switch()16?print?'gr1?is?dead?:?%s,?gr2?is?dead?:?%s'?%?(gr1.dead,?gr2.dead)17?print?gr2.switch(10)輸出:
test2 first 1
test1 finished
gr1 is dead?: True, gr2 is dead?: False
test2 back ()
gr1 is dead?: True, gr2 is dead?: True
10
從這個(gè)例子可以看出
只有當(dāng)協(xié)程對(duì)應(yīng)的函數(shù)執(zhí)行完畢,協(xié)程才會(huì)die,所以第一次Check的時(shí)候gr2并沒有die,因?yàn)榈?行切換出去了就沒切回來。在main中再switch到gr2的時(shí)候, 執(zhí)行后面的邏輯,gr2 die
如果試圖再次switch到一個(gè)已經(jīng)是dead狀態(tài)的greenlet會(huì)怎么樣呢,事實(shí)上會(huì)切換到其parent greenlet。
?
Greenlet Traceing
回到頂部
Greenlet也提供了接口使得程序員可以監(jiān)控greenlet的整個(gè)調(diào)度流程。主要是gettrace 和 settrace(callback)函數(shù)。下面看一個(gè)例子:
def?test_greenlet_tracing():????def?callback(event,?args):????????print?event,?'from',?id(args[0]),?'to',?id(args[1])????def?dummy():g2.switch()????def?dummyexception():????????raise?Exception('excep?in?coroutine')main?=?greenlet.getcurrent()g1?=?greenlet.greenlet(dummy)g2?=?greenlet.greenlet(dummyexception)????print?'main?id?%s,?gr1?id?%s,?gr2?id?%s'?%?(id(main),?id(g1),?id(g2))oldtrace?=?greenlet.settrace(callback)????try:g1.switch()????except:????????print?'Exception'finally:greenlet.settrace(oldtrace)test_greenlet_tracing()輸出:
main id 40604416, gr1 id 40604736, gr2 id 40604816
switch from 40604416 to 40604736
switch from 40604736 to 40604816
throw from 40604816 to 40604416
Exception
其中callback函數(shù)event是switch或者throw之一,表明是正常調(diào)度還是異常跑出;args是二元組,表示是從協(xié)程args[0]切換到了協(xié)程args[1]。上面的輸出展示了切換流程:從main到gr1,然后到gr2,最后回到main。
?
greenlet使用建議:
回到頂部
使用greenlet需要注意一下三點(diǎn):
第一:greenlet創(chuàng)生之后,一定要結(jié)束,不能switch出去就不回來了,否則容易造成內(nèi)存泄露
第二:python中每個(gè)線程都有自己的main greenlet及其對(duì)應(yīng)的sub-greenlet ,不能線程之間的greenlet是不能相互切換的
第三:不能存在循環(huán)引用,這個(gè)是官方文檔明確說明
”Greenlets do not participate in garbage collection; cycles involving data that is present in a greenlet’s frames will not be detected.?“
對(duì)于第一點(diǎn),我們來看一個(gè)例子:
?1?from?greenlet?import?greenlet,?GreenletExit?2?huge?=?[]?3?def?show_leak():?4?????def?test1():?5?????????gr2.switch()?6??7?????def?test2():?8?????????huge.extend([x*?x?for?x?in?range(100)])?9?????????gr1.switch()10?????????print?'finish?switch?del?huge'11?????????del?huge[:]12?????13?????gr1?=?greenlet(test1)14?????gr2?=?greenlet(test2)15?????gr1.switch()16?????gr1?=?gr2?=?None17?????print?'length?of?huge?is?zero???%s'?%?len(huge)18?19?if?__name__?==?'__main__':20?????show_leak()? 21 #?output:?length?of?huge?is?zero???100在test2函數(shù)中 第11行,我們將huge清空,然后再第16行將gr1、gr2的引用計(jì)數(shù)降到了0。但運(yùn)行結(jié)果告訴我們,第11行并沒有執(zhí)行,所以如果一個(gè)協(xié)程沒有正常結(jié)束是很危險(xiǎn)的,往往不符合程序員的預(yù)期。greenlet提供了解決這個(gè)問題的辦法,官網(wǎng)文檔提到:如果一個(gè)greenlet實(shí)例的引用計(jì)數(shù)變成0,那么會(huì)在上次掛起的地方拋出GreenletExit異常,這就使得我們可以通過try ... finally 處理資源泄露的情況。如下面的代碼:
?1?from?greenlet?import?greenlet,?GreenletExit?2?huge?=?[]?3?def?show_leak():?4?????def?test1():?5?????????gr2.switch()?6??7?????def?test2():?8?????????huge.extend([x*?x?for?x?in?range(100)])?9?????????try:10?????????????gr1.switch()11?????????finally:12?????????????print?'finish?switch?del?huge'13?????????????del?huge[:]14?????15?????gr1?=?greenlet(test1)16?????gr2?=?greenlet(test2)17?????gr1.switch()18?????gr1?=?gr2?=?None19?????print?'length?of?huge?is?zero???%s'?%?len(huge)20?21?if?__name__?==?'__main__':22?????show_leak()23?????#?output?:24?????#?finish?switch?del?huge25? ??#?length?of?huge?is?zero???0
?
上述代碼的switch流程:main greenlet --> gr1 --> gr2 --> gr1 --> main greenlet, 很明顯gr2沒有正常結(jié)束(在第10行刮起了)。第18行之后gr1,gr2的引用計(jì)數(shù)都變成0,那么會(huì)在第10行拋出GreenletExit異常,因此finally語句有機(jī)會(huì)執(zhí)行。同時(shí),在文章開始介紹Greenlet module的時(shí)候也提到了,GreenletExit這個(gè)異常并不會(huì)拋出到parent,所以main greenlet也不會(huì)出異常。
看上去貌似解決了問題,但這對(duì)程序員要求太高了,百密一疏。所以最好的辦法還是保證協(xié)程的正常結(jié)束。
轉(zhuǎn)載于:https://blog.51cto.com/jsw55667/1928862
總結(jié)
以上是生活随笔為你收集整理的greenlet 详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Harbor升级和数据库迁移手册
- 下一篇: 路由器DHCP和DHCP中继的配置