Python中的协程
Python中的協(xié)程
文章目錄
- Python中的協(xié)程
- 一、什么是協(xié)程
- 1.概念
- 2.協(xié)程的好處
- 3.缺點(diǎn)
- 二、了解協(xié)程的過(guò)程
- 1、yield工作原理
- 2.協(xié)程在運(yùn)行過(guò)程中有四個(gè)狀態(tài):
- 3、預(yù)激協(xié)程的裝飾器
- 4、終止協(xié)程和異常處理
- 5、讓協(xié)程返回值
- 6、yield from的使用
- 7、yield from的意義
- 三、greenlet的使用
- 四、gevent的使用
- 五、猴子補(bǔ)丁問(wèn)題
- 1、什么是猴子補(bǔ)丁
- 2.為什么叫做猴子補(bǔ)丁
- 3.例
- 4.gevent模塊會(huì)遇到猴子補(bǔ)丁
一、什么是協(xié)程
1.概念
協(xié)程又稱為微線程,協(xié)程是一種用戶態(tài)的輕量級(jí)線程
協(xié)程擁有自己的寄存器和棧。協(xié)程調(diào)度切換的時(shí)候,將寄存器上下文和棧都保存到其他地方,在切換回來(lái)的時(shí)候,恢復(fù)到先前保存的寄存器上下文和棧,因此:協(xié)程能保留上一次調(diào)用狀態(tài),每次過(guò)程重入時(shí),就相當(dāng)于進(jìn)入上一次調(diào)用的狀態(tài)。
2.協(xié)程的好處
1.無(wú)需線程上下文切換的開銷(還是單線程)
2.無(wú)需原子操作(一個(gè)線程改一個(gè)變量,改一個(gè)變量的過(guò)程就可以稱為原子操作)的鎖定和同步的開銷
3.方便切換控制流,簡(jiǎn)化編程模型
4.高并發(fā)+高擴(kuò)展+低成本:一個(gè)cpu支持上萬(wàn)的協(xié)程都沒有問(wèn)題,適合用于高并發(fā)處理
3.缺點(diǎn)
1.無(wú)法利用多核的資源,協(xié)程本身是個(gè)單線程,它不能同時(shí)將單個(gè)cpu的多核用上,協(xié)程需要和進(jìn)程配合才能運(yùn)用到多cpu上(協(xié)程是跑在線程上的)
2.進(jìn)行阻塞操作時(shí)會(huì)阻塞掉整個(gè)程序:如io
二、了解協(xié)程的過(guò)程
1、yield工作原理
從語(yǔ)法上來(lái)看,協(xié)程和生成器類似,都是定義體中包含yield關(guān)鍵字的函數(shù)。 yield在協(xié)程中的用法:
- 在協(xié)程中yield通常出現(xiàn)在表達(dá)式的右邊,例如:datum = yield,可以產(chǎn)出值,也可以不產(chǎn)出–如果yield關(guān)鍵字后面沒有表達(dá)式,那么生成器產(chǎn)出None。
- 在協(xié)程中yield也可能從調(diào)用方接受數(shù)據(jù),調(diào)用方是通過(guò)send(datum)的方式把數(shù)據(jù)提供給協(xié)程使用,而不是next(…)函數(shù),通常調(diào)用方會(huì)把值推送給協(xié)程。
- 協(xié)程可以把控制器讓給中心調(diào)度程序,從而激活其他的協(xié)程。
所以總體上在協(xié)程中把yield看做是控制流程的方式。
先通過(guò)一個(gè)簡(jiǎn)單的協(xié)程的例子理解:
def simple_demo():print("start")x = yieldprint("x:", x)sd = simple_demo() next(sd) sd.send(10)--------------------------->>> start >>> x: 10 >>> Traceback (most recent call last): >>> File "D:/python_projects/untitled3/xiecheng1.py", line 9, >>> in <module> >>> sd.send(10) >>> StopIteration對(duì)上述例子的分析:
- yield 的右邊沒有表達(dá)式,所以這里默認(rèn)產(chǎn)出的值是None,剛開始先調(diào)用了next(…)是因?yàn)檫@個(gè)時(shí)候生成器還沒有啟動(dòng),沒有停在yield那里,這個(gè)時(shí)候也是無(wú)法通過(guò)send發(fā)送數(shù)據(jù)。
- 所以當(dāng)我們通過(guò)next(…)激活協(xié)程后,程序就會(huì)運(yùn)行到x = yield,這里有個(gè)問(wèn)題我們需要注意,x = yield這個(gè)表達(dá)式的計(jì)算過(guò)程是先計(jì)算等號(hào)右邊的內(nèi)容,然后在進(jìn)行賦值,所以當(dāng)激活生成器后,程序會(huì)停在yield這里,但并沒有給x賦值。
- 當(dāng)我們調(diào)用send方法后yield會(huì)收到這個(gè)值并賦值給x,而當(dāng)程序運(yùn)行到協(xié)程定義體的末尾時(shí)和用生成器的時(shí)候一樣會(huì)拋出StopIteration異常
如果協(xié)程沒有通過(guò)next(...)激活(同樣我們可以通過(guò)send(None)的方式激活),但是我們直接send,會(huì)提示如下錯(cuò)誤:
def simple_demo():print("start")x = yieldprint("x:", x)sd = simple_demo() # next(sd) sd.send(10)--------------------------->>> Traceback (most recent call last): >>> File "D:/python_projects/untitled3/xiecheng1.py", line 9, >>> in <module> >>> sd.send(10) >>> TypeError: can't send non-None value to a just-started generator關(guān)于調(diào)用next(…)函數(shù)這一步通常稱為”預(yù)激(prime)“協(xié)程,即讓協(xié)程向前執(zhí)行到第一個(gè)yield表達(dá)式,準(zhǔn)備好作為活躍的協(xié)程使用
2.協(xié)程在運(yùn)行過(guò)程中有四個(gè)狀態(tài):
- GEN_CREATE:等待開始執(zhí)行
- GEN_RUNNING:解釋器正在執(zhí)行,這個(gè)狀態(tài)一般看不到
- GEN_SUSPENDED:在yield表達(dá)式處暫停
- GEN_CLOSED:執(zhí)行結(jié)束
通過(guò)下面例子來(lái)查看協(xié)程的狀態(tài):
可以通過(guò)注釋理解這個(gè)例子。
接著再通過(guò)一個(gè)計(jì)算平均值的例子來(lái)繼續(xù)理解:
>>> def averager():total = 0.0count = 0average = Nonewhile True:term = yield averagetotal += termcount += 1average = total/count>>> avg = averager() >>> next(avg) >>> avg.send(10) 10.0 >>> avg.send(30) 20.0 >>> avg.send(40) 26.666666666666668這里是一個(gè)死循環(huán),只要不停send值給協(xié)程,可以一直計(jì)算下去。
通過(guò)上面的幾個(gè)例子我們發(fā)現(xiàn),我們?nèi)绻胍_始使用協(xié)程的時(shí)候必須通過(guò)next(...)方式激活協(xié)程,如果不預(yù)激,這個(gè)協(xié)程就無(wú)法使用,如果哪天在代碼中遺忘了那么就出問(wèn)題了,所以有一種預(yù)激協(xié)程的裝飾器,可以幫助我們干這件事。
3、預(yù)激協(xié)程的裝飾器
下面是預(yù)激裝飾器的演示例子:
from functools import wrapsdef coroutine(func):@wraps(func)def primer(*args,**kwargs):gen = func(*args,**kwargs)next(gen)return genreturn primer@coroutine def averager():total = 0.0count = 0average = Nonewhile True:term = yield averagetotal += termcount += 1average = total/countcoro_avg = averager() from inspect import getgeneratorstate print(getgeneratorstate(coro_avg)) print(coro_avg.send(10)) print(coro_avg.send(30)) print(coro_avg.send(5))--------------------------->>> GEN_SUSPENDED >>> 10.0 >>> 20.0 >>> 15.0關(guān)于預(yù)激,在使用yield from句法調(diào)用協(xié)程的時(shí)候,會(huì)自動(dòng)預(yù)激活,這樣其實(shí)與我們上面定義的coroutine裝飾器是不兼容的,在python3.4里面的asyncio.coroutine裝飾器不會(huì)預(yù)激協(xié)程,因此兼容yield from
4、終止協(xié)程和異常處理
協(xié)程中未處理的異常會(huì)向上冒泡,傳給 next 函數(shù)或 send 方法的調(diào)用方(即觸發(fā)協(xié)程的對(duì)象)。
繼續(xù)使用上面averager的例子
>>> coro_avg = averager() >>> coro_avg.send(40) 40.0 >>> coro_avg.send(50) 45.0 >>> coro_avg.send('spam') Traceback (most recent call last): ... TypeError: unsupported operand type(s) for +=: 'float' and 'str' >>> coro_avg.send(60) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration由于在協(xié)程內(nèi)沒有處理異常,協(xié)程會(huì)終止。如果試圖重新激活協(xié)程,會(huì)拋出StopIteration 異常。
從 Python 2.5 開始,客戶代碼可以在生成器對(duì)象上調(diào)用兩個(gè)方法:throw 和 close,顯式地把異常發(fā)給協(xié)程。
1:generator.throw(exc_type[, exc_value[, traceback]])使生成器在暫停的 yield 表達(dá)式處拋出指定的異常。如果生成器處理了拋出的異常,代碼會(huì)向前執(zhí)行到下一個(gè) yield 表達(dá)式,而產(chǎn)出的值會(huì)成為調(diào)用 generator.throw方法得到的返回值。如果生成器沒有處理拋出的異常,異常會(huì)向上冒泡,傳到調(diào)用方的上下文中。
2:generator.close()使生成器在暫停的 yield 表達(dá)式處拋出 GeneratorExit 異常。如果生成器沒有處理這個(gè)異常,或者拋出了 StopIteration 異常(通常是指運(yùn)行到結(jié)尾),調(diào)用方不會(huì)報(bào)錯(cuò)。如果收到 GeneratorExit 異常,生成器一定不能產(chǎn)出值,否則解釋器會(huì)拋出RuntimeError 異常。生成器拋出的其他異常會(huì)向上冒泡,傳給調(diào)用方。
示例如下:
from inspect import getgeneratorstate class DemoException(Exception):"""為這次演示定義的異常類型。"""passdef demo_exc_handling():print('-> coroutine started')while True:try:x = yieldexcept DemoException:print('*** DemoException handled. Continuing...')else:print('-> coroutine received: {!r}'.format(x))raise RuntimeError('This line should never run.')>>> exc_coro = demo_exc_handling() >>> next(exc_coro) -> coroutine started >>> exc_coro.send(11) -> coroutine received: 11 >>> exc_coro.send(22) -> coroutine received: 22>>> exc_coro.throw(DemoException) *** DemoException handled. Continuing... >>> getgeneratorstate(exc_coro) 'GEN_SUSPENDED' >>> exc_coro.close() >>> getgeneratorstate(exc_coro) 'GEN_CLOSED'5、讓協(xié)程返回值
在Python2中,生成器函數(shù)中的return不允許返回附帶返回值。在Python3中取消了這一限制,因而允許協(xié)程可以返回值:
from collections import namedtuple Result = namedtuple('Result', 'count average')def averager():total = 0.0count = 0average = Nonewhile True:term = yieldif term is None:breaktotal += termcount += 1average = total/countreturn Result(count, average)>>> coro_avg = averager() >>> next(coro_avg) >>> coro_avg.send(10) >>> coro_avg.send(30) >>> coro_avg.send(6.5) >>> coro_avg.send(None) Traceback (most recent call last): ... StopIteration: Result(count=3, average=15.5)發(fā)送 None 會(huì)終止循環(huán),導(dǎo)致協(xié)程結(jié)束,返回結(jié)果。一如既往,生成器對(duì)象會(huì)拋出StopIteration 異常。異常對(duì)象的 value 屬性保存著返回的值。
注意,return 表達(dá)式的值會(huì)偷偷傳給調(diào)用方,賦值給 StopIteration 異常的一個(gè)屬性。這樣做有點(diǎn)不合常理,但是能保留生成器對(duì)象的常規(guī)行為——耗盡時(shí)拋出StopIteration 異常。如果需要接收返回值,可以這樣:
>>> try: ... coro_avg.send(None) ... except StopIteration as exc: ... result = exc.value ... >>> result >Result(count=3, average=15.5)獲取協(xié)程的返回值要繞個(gè)圈子,可以使用Python3.3引入的yield from獲取返回值。yield from 結(jié)構(gòu)會(huì)在內(nèi)部自動(dòng)捕獲 StopIteration 異常。這種處理方式與 for 循環(huán)處理 StopIteration 異常的方式一樣。對(duì) yield from 結(jié)構(gòu)來(lái)說(shuō),解釋器不僅會(huì)捕獲 StopIteration 異常,還會(huì)把value 屬性的值變成 yield from 表達(dá)式的值。
6、yield from的使用
yield from 是 Python3.3 后新加的語(yǔ)言結(jié)構(gòu)。在其他語(yǔ)言中,類似的結(jié)構(gòu)使用 await 關(guān)鍵字,這個(gè)名稱好多了,因?yàn)樗鼈鬟_(dá)了至關(guān)重要的一點(diǎn):在生成器 gen 中使用 yield from subgen() 時(shí),subgen 會(huì)獲得控制權(quán),把產(chǎn)出的值傳給 gen 的調(diào)用方,即調(diào)用方可以直接控制 subgen。與此同時(shí),gen 會(huì)阻塞,等待 subgen 終止。
yield from 可用于簡(jiǎn)化 for 循環(huán)中的 yield 表達(dá)式。例如:
>>> def gen(): ... for c in 'AB': ... yield c ... for i in range(1, 3): ... yield i ... >>> list(gen()) ['A', 'B', 1, 2] 可以改為>>> def gen(): ... yield from 'AB' ... yield from range(1, 3) ... >>> list(gen()) ['A', 'B', 1, 2]- yield from x 表達(dá)式對(duì) x 對(duì)象所做的第一件事是,調(diào)用 iter(x),從中獲取迭代器。因此,x可以是任何可迭代的對(duì)象。
- 如果 yield from 結(jié)構(gòu)唯一的作用是替代產(chǎn)出值的嵌套 for 循環(huán),這個(gè)結(jié)構(gòu)很有可能不會(huì)添加到 Python 語(yǔ)言中。
- yield from 的主要功能是打開雙向通道,把最外層的調(diào)用方與最內(nèi)層的子生成器連接起來(lái),這樣二者可以直接發(fā)送和產(chǎn)出值,還可以直接傳入異常,而不用在位于中間的協(xié)程中添加大量處理異常的樣板代碼。有了這個(gè)結(jié)構(gòu),協(xié)程可以通過(guò)以前不可能的方式委托職責(zé)。
PEP 380 使用了一些yield from使用的專門術(shù)語(yǔ):
- 委派生成器:包含 yield from 表達(dá)式的生成器函數(shù);
- 子生成器:從 yield from 表達(dá)式中 部分獲取的生成器;
- 調(diào)用方:調(diào)用委派生成器的客戶端代碼;
- 委派生成器在 yield from 表達(dá)式處暫停時(shí),調(diào)用方可以直接把數(shù)據(jù)發(fā)給子生成器,子生成器再把產(chǎn)出的值發(fā)給調(diào)用方。子生成器返回之后,解釋器會(huì)拋出StopIteration 異常,并把返回值附加到異常對(duì)象上,此時(shí)委派生成器會(huì)恢復(fù)。
下面是一個(gè)求平均身高和體重的示例代碼:
from collections import namedtupleResult = namedtuple('Result', 'count average')# 子生成器 def averager():total = 0.0count = 0average = Nonewhile True:# main 函數(shù)發(fā)送數(shù)據(jù)到這里 print("in averager, before yield")term = yieldif term is None: # 終止條件breaktotal += termcount += 1average = total/countprint("in averager, return result")return Result(count, average) # 返回的Result 會(huì)成為grouper函數(shù)中yield from表達(dá)式的值 # 委派生成器 def grouper(results, key):# 這個(gè)循環(huán)每次都會(huì)新建一個(gè)averager 實(shí)例,每個(gè)實(shí)例都是作為協(xié)程使用的生成器對(duì)象while True:print("in grouper, before yield from averager, key is ", key)results[key] = yield from averager()print("in grouper, after yield from, key is ", key) # 調(diào)用方 def main(data):results = {}for key, values in data.items():# group 是調(diào)用grouper函數(shù)得到的生成器對(duì)象group = grouper(results, key)print("\ncreate group: ", group)next(group) #預(yù)激 group 協(xié)程。print("pre active group ok")for value in values:# 把各個(gè)value傳給grouper 傳入的值最終到達(dá)averager函數(shù)中;# grouper并不知道傳入的是什么,同時(shí)grouper實(shí)例在yield from處暫停print("send to %r value %f now"%(group, value))group.send(value)# 把None傳入groupper,傳入的值最終到達(dá)averager函數(shù)中,導(dǎo)致當(dāng)前實(shí)例終止。然后繼續(xù)創(chuàng)建下一個(gè)實(shí)例。# 如果沒有g(shù)roup.send(None),那么averager子生成器永遠(yuǎn)不會(huì)終止,委派生成器也永遠(yuǎn)不會(huì)在此激活,也就不會(huì)為result[key]賦值print("send to %r none"%group)group.send(None)print("report result: ")report(results) # 輸出報(bào)告 def report(results):for key, result in sorted(results.items()):group, unit = key.split(';')print('{:2} {:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit))data = {'girls;kg':[40, 41, 42, 43, 44, 54],'girls;m': [1.5, 1.6, 1.8, 1.5, 1.45, 1.6],'boys;kg':[50, 51, 62, 53, 54, 54],'boys;m': [1.6, 1.8, 1.8, 1.7, 1.55, 1.6], }if __name__ == '__main__':main(data)- grouper 發(fā)送的每個(gè)值都會(huì)經(jīng)由 yield from 處理,通過(guò)管道傳給 averager 實(shí)例。
- grouper 會(huì)在 yield from 表達(dá)式處暫停,等待 averager 實(shí)例處理客戶端發(fā)來(lái)的值。
- averager 實(shí)例運(yùn)行完畢后,返回的值綁定到 results[key] 上。
- while 循環(huán)會(huì)不斷創(chuàng)建 averager 實(shí)例,處理更多的值。
- 外層 for 循環(huán)重新迭代時(shí)會(huì)新建一個(gè) grouper 實(shí)例,然后綁定到 group 變量上。前一個(gè) grouper 實(shí)例(以及它創(chuàng)建的尚未終止的 averager 子生成器實(shí)例)被垃圾回收程序回收。
7、yield from的意義
- 把迭代器當(dāng)作生成器使用,相當(dāng)于把子生成器的定義體內(nèi)聯(lián)在 yield from 表達(dá)式中。此外,子生成器可以執(zhí)行 return語(yǔ)句,返回一個(gè)值,而返回的值會(huì)成為 yield from 表達(dá)式的值。
- 子生成器產(chǎn)出的值都直接傳給委派生成器的調(diào)用方(即客戶端代碼);
- 使用 send() 方法發(fā)給委派生成器的值都直接傳給子生成器。如果發(fā)送的值是None,那么會(huì)調(diào)用子生成器的 next() 方法。如果發(fā)送的值不是 None,那么會(huì)調(diào)用子生成器的 send() 方法。如果子生成器拋出 StopIteration 異常,那么委派生成器恢復(fù)運(yùn)行。任何其他異常都會(huì)向上冒泡,傳給委派生成器;
- 生成器退出時(shí),生成器(或子生成器)中的 return expr 表達(dá)式會(huì)觸發(fā)StopIteration(expr) 異常拋出;
- yield from 表達(dá)式的值是子生成器終止時(shí)傳給 StopIteration 異常的第一個(gè)參數(shù)。
- yield from 的具體語(yǔ)義很難理解,尤其是處理異常的那兩點(diǎn)。在PEP 380 中闡述了 yield from 的語(yǔ)義。還使用偽代碼(使用 Python 句法)演示了 yield from 的行為。
- 若想研究那段偽代碼,最好將其簡(jiǎn)化,只涵蓋 yield from 最基本且最常見的用法:yield from 出現(xiàn)在委派生成器中,客戶端代碼驅(qū)動(dòng)著委派生成器,而委派生成器驅(qū)動(dòng)著子生成器。
- 為了簡(jiǎn)化涉及到的邏輯,假設(shè)客戶端沒有在委派生成器上調(diào)用throw(…) 或 close() 方法。而且假設(shè)子生成器不會(huì)拋出異常,而是一直運(yùn)行到終止,讓解釋器拋出 StopIteration 異常。上面示例中的腳本就做了這些簡(jiǎn)化邏輯的假設(shè)。
下面的偽代碼,等效于委派生成器中的 RESULT = yield from EXPR 語(yǔ)句(這里針對(duì)的是最簡(jiǎn)單的情況:不支持 .throw(…) 和 .close() 方法,而且只處理 StopIteration 異常):
_i = iter(EXPR) try:_y = next(_i) except StopIteration as _e:_r = _e.value else:while 1:_s = yield _ytry:_y = _i.send(_s)except StopIteration as _e:_r = _e.valuebreak RESULT = _r- 但是,現(xiàn)實(shí)情況要復(fù)雜一些,因?yàn)橐幚砜蛻魧?duì) throw(...) 和 close()方法的調(diào)用,而這兩個(gè)方法執(zhí)行的操作必須傳入子生成器。
- 此外,子生成器可能只是純粹的迭代器,不支持 throw(…) 和 close() 方法,因此 yield from 結(jié)構(gòu)的邏輯必須處理這種情況。
- 如果子生成器實(shí)現(xiàn)了這兩個(gè)方法,而在子生成器內(nèi)部,這兩個(gè)方法都會(huì)觸發(fā)異常拋出,這種情況也必須由 yield from 機(jī)制處理。
- 調(diào)用方可能會(huì)無(wú)緣無(wú)故地讓子生成器自己拋出異常,實(shí)現(xiàn) yield from 結(jié)構(gòu)時(shí)也必須處理這種情況。
- 最后,為了優(yōu)化,如果調(diào)用方調(diào)用 next(...) 函數(shù)或 .send(None) 方法,都要轉(zhuǎn)交職責(zé),在子生成器上調(diào)用next(...) 函數(shù);僅當(dāng)調(diào)用方發(fā)送的值不是 None 時(shí),才使用子生成器的 .send(...) 方法。
下面的偽代碼,是考慮了上述情況之后,語(yǔ)句:RESULT = yield from EXPR的等效代碼:
_i = iter(EXPR) try:_y = next(_i) except StopIteration as _e:_r = _e.value else:while 1:try:_s = yield _yexcept GeneratorExit as _e:try:_m = _i.closeexcept AttributeError:passelse:_m()raise _eexcept BaseException as _e:_x = sys.exc_info()try:_m = _i.throwexcept AttributeError:raise _eelse:try:_y = _m(*_x)except StopIteration as _e:_r = _e.valuebreakelse:try:if _s is None:_y = next(_i)else:_y = _i.send(_s)except StopIteration as _e:_r = _e.valuebreak RESULT = _r上面的偽代碼中,會(huì)預(yù)激子生成器。這表明,用于自動(dòng)預(yù)激的裝飾器與 yield from 結(jié)構(gòu)不兼容。
三、greenlet的使用
python中為實(shí)現(xiàn)協(xié)程封裝了一些非常好用的包,首先介紹greenlet的使用。
Greenlet是python的一個(gè)C擴(kuò)展,旨在提供可自行調(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來(lái)表示協(xié)程的切換,從一個(gè)協(xié)程切換到另一個(gè)協(xié)程需要顯式指定。
以下例子:
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()--------------------------->>> 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ì)被掛起,等到切換回來(lái)(在test2中切換回來(lái) 打印34)。
- 當(dāng)這個(gè)協(xié)程對(duì)應(yīng)函數(shù)執(zhí)行完畢,那么這個(gè)協(xié)程就變成dead狀態(tài)。
- 對(duì)于greenlet,最常用的寫法是 x = gr.switch(y)。 這句話的意思是切換到gr,傳入?yún)?shù)y。當(dāng)從其他協(xié)程(不一定是這個(gè)gr)切換回來(lái)的時(shí)候,將值付給x。
- 上面的例子,第12行從maingreenlet切換到了gr1,test1第3行切換到了gs2,然后gr1掛起,第8行從gr2切回gr1時(shí),將值(10)返回值給了z。
- 使用greenlet需要注意一下三點(diǎn):
- 第一:greenlet創(chuàng)生之后,一定要結(jié)束,不能switch出去就不回來(lái)了,否則容易造成內(nèi)存泄露
- 第二:python中每個(gè)線程都有自己的main greenlet及其對(duì)應(yīng)的sub-greenlet ,不能線程之間的greenlet是不能相互切換的
- 第三:不能存在循環(huán)引用,這個(gè)是官方文檔明確說(shuō)明
四、gevent的使用
gevent可以自動(dòng)捕獲I/O耗時(shí)操作,來(lái)自動(dòng)切換協(xié)程任務(wù)。
import geventdef f1():for i in range(5):print('run func: f1, index: %s ' % i)gevent.sleep(1)def f2():for i in range(5):print('run func: f2, index: %s ' % i)gevent.sleep(1)t1 = gevent.spawn(f1) t2 = gevent.spawn(f2) gevent.joinall([t1, t2])------------------------------>>> run func: f1, index: 0 >>> run func: f2, index: 0 >>> run func: f1, index: 1 >>> run func: f2, index: 1 >>> run func: f1, index: 2 >>> run func: f2, index: 2 >>> run func: f1, index: 3 >>> run func: f2, index: 3 >>> run func: f1, index: 4 >>> run func: f2, index: 4- 可以看出,f1和f2是交叉打印信息的,因?yàn)樵诖a執(zhí)行的過(guò)程中,我們?nèi)藶槭褂胓event.sleep(0)創(chuàng)建了一個(gè)阻塞,gevent在運(yùn)行到這里時(shí)就會(huì)自動(dòng)切換函數(shù)切換函數(shù)。
- 也可以在執(zhí)行的時(shí)候sleep更長(zhǎng)時(shí)間,可以發(fā)現(xiàn)兩個(gè)函數(shù)基本是同時(shí)運(yùn)行然后各自待。
五、猴子補(bǔ)丁問(wèn)題
1、什么是猴子補(bǔ)丁
在運(yùn)行時(shí)對(duì)方法 / 類 / 屬性 / 功能進(jìn)行修改,把新的代碼作為解決方案代替原有的程序,也就是為其打上補(bǔ)丁。
2.為什么叫做猴子補(bǔ)丁
一種說(shuō)法雜牌軍、游擊隊(duì)的英文發(fā)音與猩猩相似,雜牌軍、游擊隊(duì)不是原裝軍隊(duì),就像是替補(bǔ),所以也就演變叫做猴子補(bǔ)丁
另一種說(shuō)法“monkeying about”有胡鬧,頑皮,哄騙的意思,所以叫做猴子補(bǔ)丁
python中使用猴子補(bǔ)丁
3.例
class Example():def func1(self):print('我才是原裝')def func2(*args):print('我要取代你')def func3(*args):print('都給我一邊去')instance = Example() Example.func1 = func2 instance.func1() # 我要取代你 instance.func1 = func3 instance.func1() # 都給我一邊去 instance2 = Example() instance2.func1() # 我要取代你例子非常簡(jiǎn)單,func2取代的是類的方法,func3取代的是實(shí)例的方法,最終輸出都不是原裝
4.gevent模塊會(huì)遇到猴子補(bǔ)丁
import gevent.monkeygevent.monkey.patch_all()使用猴子補(bǔ)丁的方式,gevent能夠修改標(biāo)準(zhǔn)庫(kù)里面大部分的阻塞式系統(tǒng)調(diào)用,包括socket、ssl、threading和 select等模塊,而變?yōu)閰f(xié)作式運(yùn)行。也就是通過(guò)猴子補(bǔ)丁的monkey.patch_xxx()來(lái)將python標(biāo)準(zhǔn)庫(kù)中模塊或函數(shù)改成gevent中的響應(yīng)的具有協(xié)程的協(xié)作式對(duì)象。這樣在不改變?cè)写a的情況下,將應(yīng)用的阻塞式方法,變成協(xié)程式的。
總結(jié)
以上是生活随笔為你收集整理的Python中的协程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python中的线程间通信
- 下一篇: Python中的网络编程之UDP