深入理解python協程
一、什麼是協程
協程擁有自己的寄存器和棧。協程調度切換的時候,將寄存器上下文和棧都保存到其他地方,在切換回來的時候,恢復到先前保存的寄存器上下文和棧,因此:協程能保留上一次調用狀態,每次過程重入時,就相當於進入上一次調用的狀態。
協程的好處:
- 1.無需線程上下文切換的開銷(還是單線程)
- 2.無需原子操作(一個線程改一個變量,改一個變量的過程就可以稱為原子操作)的鎖定和同步的開銷
- 3.方便切換控制流,簡化編程模型
- 4.高並發+高擴展+低成本:一個cpu支持上萬的協程都沒有問題,適合用於高並發處理
缺點:
- 1.無法利用多核的資源,協程本身是個單線程,它不能同時將單個cpu的多核用上,協程需要和進程配合才能運用到多cpu上(協程是跑在線程上的)
- 2.進行阻塞操作時會阻塞掉整個程序:如io
二、瞭解協程的過程
2.1、yield工作原理
從語法上來看,協程和生成器類似,都是定義體中包含yield關鍵字的函數。
yield在協程中的用法:
- 在協程中yield通常出現在表達式的右邊,例如:datum = yield,可以產出值,也可以不產出–如果yield關鍵字後面沒有表達式,那麼生成器產出None。
- 在協程中yield也可能從調用方接受數據,調用方是通過send(datum)的方式把數據提供給協程使用,而不是next(…)函數,通常調用方會把值推送給協程。
- 協程可以把控制器讓給中心調度程序,從而激活其他的協程。
所以總體上在協程中把yield看做是控制流程的方式。
先通過一個簡單的協程的例子理解:
def simple_demo(): print("start") x = yield print("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
對上述例子的分析:
yield 的右邊沒有表達式,所以這裡默認產出的值是None
剛開始先調用瞭next(…)是因為這個時候生成器還沒有啟動,沒有停在yield那裡,這個時候也是無法通過send發送數據。所以當我們通過next(…)激活協程後,程序就會運行到x = yield,這裡有個問題我們需要註意,x = yield這個表達式的計算過程是先計算等號右邊的內容,然後在進行賦值,所以當激活生成器後,程序會停在yield這裡,但並沒有給x賦值。
當我們調用send方法後yield會收到這個值並賦值給x,而當程序運行到協程定義體的末尾時和用生成器的時候一樣會拋出StopIteration異常
如果協程沒有通過next(…)激活(同樣我們可以通過send(None)的方式激活),但是我們直接send,會提示如下錯誤:
def simple_demo(): print("start") x = yield print("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
關於調用next(…)函數這一步通常稱為”預激(prime)“協程,即讓協程向前執行到第一個yield表達式,準備好作為活躍的協程使用
協程在運行過程中有四個狀態:
- GEN_CREATE:等待開始執行
- GEN_RUNNING:解釋器正在執行,這個狀態一般看不到
- GEN_SUSPENDED:在yield表達式處暫停
- GEN_CLOSED:執行結束
通過下面例子來查看協程的狀態:
>>> from inspect import getgeneratorstate >>> def simple_demo(a): print("start: a = ", a) b = yield a print("b = ", b) c = yield a + b print("c = ", c) >>> sd = simple_demo(2) >>> print(getgeneratorstate(sd)) GEN_CREATED >>> next(sd) # 預激協程,使它走到第一個yield處,因為第一個yield處有yield值a,所以返回a的值,然後在此yield處阻塞 start: a = 2 2 >>> print(getgeneratorstate(sd)) GEN_SUSPENDED >>> sd.send(3) # 發送3,進入協程接著上一次阻塞的yield處執行,yield接收參數3賦值給b,到下一個yield處返回a+b的值,然後在此yield處再次阻塞,等待下次send值 b = 3 5 >>> sd.send(4) # 同上一次send過程,到此結束拋異常 c = 4 Traceback (most recent call last): File "<pyshell#8>", line 1, in <module> sd.send(4) StopIteration >>> print(getgeneratorstate(sd)) GEN_CLOSED
可以通過註釋理解這個例子。
接著再通過一個計算平均值的例子來繼續理解:
>>> def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count >>> avg = averager() >>> next(avg) >>> avg.send(10) 10.0 >>> avg.send(30) 20.0 >>> avg.send(40) 26.666666666666668
這裡是一個死循環,隻要不停send值給協程,可以一直計算下去。
通過上面的幾個例子我們發現,我們如果想要開始使用協程的時候必須通過next(…)方式激活協程,如果不預激,這個協程就無法使用,如果哪天在代碼中遺忘瞭那麼就出問題瞭,所以有一種預激協程的裝飾器,可以幫助我們幹這件事。
2.2、預激協程的裝飾器
下面是預激裝飾器的演示例子:
from functools import wraps def coroutine(func): @wraps(func) def primer(*args,**kwargs): gen = func(*args,**kwargs) next(gen) return gen return primer @coroutine def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count coro_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
關於預激,在使用yield from句法調用協程的時候,會自動預激活,這樣其實與我們上面定義的coroutine裝飾器是不兼容的,在python3.4裡面的asyncio.coroutine裝飾器不會預激協程,因此兼容yield from
2.3、終止協程和異常處理
協程中未處理的異常會向上冒泡,傳給 next 函數或 send 方法的調用方(即觸發協程的對象)。
繼續使用上面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
由於在協程內沒有處理異常,協程會終止。如果試圖重新激活協程,會拋出StopIteration 異常。
從 Python 2.5 開始,客戶代碼可以在生成器對象上調用兩個方法:throw 和 close,顯式地把異常發給協程。
1:generator.throw(exc_type[, exc_value[, traceback]])
使生成器在暫停的 yield 表達式處拋出指定的異常。如果生成器處理瞭拋出的異常,代碼會向前執行到下一個 yield 表達式,而產出的值會成為調用 generator.throw方法得到的返回值。如果生成器沒有處理拋出的異常,異常會向上冒泡,傳到調用方的上下文中。
2:generator.close()
使生成器在暫停的 yield 表達式處拋出 GeneratorExit 異常。如果生成器沒有處理這個異常,或者拋出瞭 StopIteration 異常(通常是指運行到結尾),調用方不會報錯。如果收到 GeneratorExit 異常,生成器一定不能產出值,否則解釋器會拋出RuntimeError 異常。生成器拋出的其他異常會向上冒泡,傳給調用方。
示例如下:
from inspect import getgeneratorstate class DemoException(Exception): """為這次演示定義的異常類型。""" pass def demo_exc_handling(): print('-> coroutine started') while True: try: x = yield except 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'
2.4、讓協程返回值
在Python2中,生成器函數中的return不允許返回附帶返回值。在Python3中取消瞭這一限制,因而允許協程可以返回值:
from collections import namedtuple Result = namedtuple('Result', 'count average') def averager(): total = 0.0 count = 0 average = None while True: term = yield if term is None: break total += term count += 1 average = total/count return 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)
發送 None 會終止循環,導致協程結束,返回結果。一如既往,生成器對象會拋出StopIteration 異常。異常對象的 value 屬性保存著返回的值。
註意,return 表達式的值會偷偷傳給調用方,賦值給 StopIteration 異常的一個屬性。這樣做有點不合常理,但是能保留生成器對象的常規行為——耗盡時拋出StopIteration 異常。如果需要接收返回值,可以這樣:
>>> try: ... coro_avg.send(None) ... except StopIteration as exc: ... result = exc.value ... >>> result Result(count=3, average=15.5)
獲取協程的返回值要繞個圈子,可以使用Python3.3引入的yield from獲取返回值。yield from 結構會在內部自動捕獲 StopIteration 異常。這種處理方式與 for 循環處理 StopIteration 異常的方式一樣。對 yield from 結構來說,解釋器不僅會捕獲 StopIteration 異常,還會把value 屬性的值變成 yield from 表達式的值。
2.5、yield from的使用
yield from 是 Python3.3 後新加的語言結構。在其他語言中,類似的結構使用 await 關鍵字,這個名稱好多瞭,因為它傳達瞭至關重要的一點:在生成器 gen 中使用 yield from subgen() 時,subgen 會獲得控制權,把產出的值傳給 gen 的調用方,即調用方可以直接控制 subgen。與此同時,gen 會阻塞,等待 subgen 終止。
yield from 可用於簡化 for 循環中的 yield 表達式。例如:
>>> 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 表達式對 x 對象所做的第一件事是,調用 iter(x),從中獲取迭代器。因此,x 可以是任何可迭代的對象。
如果 yield from 結構唯一的作用是替代產出值的嵌套 for 循環,這個結構很有可能不會添加到 Python 語言中。
yield from 的主要功能是打開雙向通道,把最外層的調用方與最內層的子生成器連接起來,這樣二者可以直接發送和產出值,還可以直接傳入異常,而不用在位於中間的協程中添加大量處理異常的樣板代碼。有瞭這個結構,協程可以通過以前不可能的方式委托職責。
PEP 380 使用瞭一些yield from使用的專門術語:
- 委派生成器:包含 yield from 表達式的生成器函數;
- 子生成器:從 yield from 表達式中 部分獲取的生成器;
- 調用方:調用委派生成器的客戶端代碼;
委派生成器在 yield from 表達式處暫停時,調用方可以直接把數據發給子生成器,子生成器再把產出的值發給調用方。子生成器返回之後,解釋器會拋出StopIteration 異常,並把返回值附加到異常對象上,此時委派生成器會恢復。
下面是一個求平均身高和體重的示例代碼:
from collections import namedtuple Result = namedtuple('Result', 'count average') # 子生成器 def averager(): total = 0.0 count = 0 average = None while True: # main 函數發送數據到這裡 print("in averager, before yield") term = yield if term is None: # 終止條件 break total += term count += 1 average = total/count print("in averager, return result") return Result(count, average) # 返回的Result 會成為grouper函數中yield from表達式的值 # 委派生成器 def grouper(results, key): # 這個循環每次都會新建一個averager 實例,每個實例都是作為協程使用的生成器對象 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) # 調用方 def main(data): results = {} for key, values in data.items(): # group 是調用grouper函數得到的生成器對象 group = grouper(results, key) print("\ncreate group: ", group) next(group) #預激 group 協程。 print("pre active group ok") for value in values: # 把各個value傳給grouper 傳入的值最終到達averager函數中; # grouper並不知道傳入的是什麼,同時grouper實例在yield from處暫停 print("send to %r value %f now"%(group, value)) group.send(value) # 把None傳入groupper,傳入的值最終到達averager函數中,導致當前實例終止。然後繼續創建下一個實例。 # 如果沒有group.send(None),那麼averager子生成器永遠不會終止,委派生成器也永遠不會在此激活,也就不會為result[key]賦值 print("send to %r none"%group) group.send(None) print("report result: ") report(results) # 輸出報告 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 發送的每個值都會經由 yield from 處理,通過管道傳給 averager 實例。grouper 會在 yield from 表達式處暫停,等待 averager 實例處理客戶端發來的值。averager 實例運行完畢後,返回的值綁定到 results[key] 上。while 循環會不斷創建 averager 實例,處理更多的值。
外層 for 循環重新迭代時會新建一個 grouper 實例,然後綁定到 group 變量上。前一個 grouper 實例(以及它創建的尚未終止的 averager 子生成器實例)被垃圾回收程序回收。
代碼結果如下:
create group: <generator object grouper at 0x7f34ce8458e0>
in grouper, before yield from averager, key is girls;kg
in averager, before yield
pre active group ok
send to <generator object grouper at 0x7f34ce8458e0> value 40.000000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce8458e0> value 41.000000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce8458e0> value 42.000000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce8458e0> value 43.000000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce8458e0> value 44.000000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce8458e0> value 54.000000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce8458e0> none
in averager, return result
in grouper, after yield from, key is girls;kg
in grouper, before yield from averager, key is girls;kg
in averager, before yield
create group: <generator object grouper at 0x7f34ce845678>
in grouper, before yield from averager, key is girls;m
in averager, before yield
pre active group ok
send to <generator object grouper at 0x7f34ce845678> value 1.500000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce845678> value 1.600000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce845678> value 1.800000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce845678> value 1.500000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce845678> value 1.450000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce845678> value 1.600000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce845678> none
in averager, return result
in grouper, after yield from, key is girls;m
in grouper, before yield from averager, key is girls;m
in averager, before yield
create group: <generator object grouper at 0x7f34ce845620>
in grouper, before yield from averager, key is boys;kg
in averager, before yield
pre active group ok
send to <generator object grouper at 0x7f34ce845620> value 50.000000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce845620> value 51.000000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce845620> value 62.000000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce845620> value 53.000000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce845620> value 54.000000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce845620> value 54.000000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce845620> none
in averager, return result
in grouper, after yield from, key is boys;kg
in grouper, before yield from averager, key is boys;kg
in averager, before yield
create group: <generator object grouper at 0x7f34ce8458e0>
in grouper, before yield from averager, key is boys;m
in averager, before yield
pre active group ok
send to <generator object grouper at 0x7f34ce8458e0> value 1.600000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce8458e0> value 1.800000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce8458e0> value 1.800000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce8458e0> value 1.700000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce8458e0> value 1.550000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce8458e0> value 1.600000 now
in averager, before yield
send to <generator object grouper at 0x7f34ce8458e0> none
in averager, return result
in grouper, after yield from, key is boys;m
in grouper, before yield from averager, key is boys;m
in averager, before yield
report result:
6 boys averaging 54.00kg
6 boys averaging 1.68m
6 girls averaging 44.00kg
6 girls averaging 1.58m
這個試驗想表明的關鍵一點是,如果子生成器不終止,委派生成器會在yield from 表達式處永遠暫停。如果是這樣,程序不會向前執行,因為 yield from(與 yield 一樣)把控制權轉交給客戶代碼(即,委派生成器的調用方)瞭。
2.6、yield from的意義
把迭代器當作生成器使用,相當於把子生成器的定義體內聯在 yield from 表達式中。此外,子生成器可以執行 return 語句,返回一個值,而返回的值會成為 yield from 表達式的值。
PEP 380 在“Proposal”一節(https://www.python.org/dev/peps/pep-0380/#proposal)分六點說明瞭 yield from 的行為。這裡幾乎原封不動地引述,不過把有歧義的“迭代器”一詞都換成瞭“子生成器”,還做瞭進一步說明。上面的示例闡明瞭下述四點:
子生成器產出的值都直接傳給委派生成器的調用方(即客戶端代碼);
使用 send() 方法發給委派生成器的值都直接傳給子生成器。如果發送的值是None,那麼會調用子生成器的 next() 方法。如果發送的值不是 None,那麼會調用子生成器的 send() 方法。如果子生成器拋出 StopIteration 異常,那麼委派生成器恢復運行。任何其他異常都會向上冒泡,傳給委派生成器;
生成器退出時,生成器(或子生成器)中的 return expr 表達式會觸發StopIteration(expr) 異常拋出;
yield from 表達式的值是子生成器終止時傳給 StopIteration 異常的第一個參數。
yield from 的具體語義很難理解,尤其是處理異常的那兩點。在PEP 380 中闡述瞭 yield from 的語義。還使用偽代碼(使用 Python 句法)演示瞭 yield from 的行為。
若想研究那段偽代碼,最好將其簡化,隻涵蓋 yield from 最基本且最常見的用法:yield from 出現在委派生成器中,客戶端代碼驅動著委派生成器,而委派生成器驅動著子生成器。為瞭簡化涉及到的邏輯,假設客戶端沒有在委派生成器上調用throw(…) 或 close() 方法。而且假設子生成器不會拋出異常,而是一直運行到終止,讓解釋器拋出 StopIteration 異常。上面示例中的腳本就做瞭這些簡化邏輯的假設。
下面的偽代碼,等效於委派生成器中的 RESULT = yield from EXPR 語句(這裡針對的是最簡單的情況:不支持 .throw(…) 和 .close() 方法,而且隻處理 StopIteration 異常):
_i = iter(EXPR) try: _y = next(_i) except StopIteration as _e: _r = _e.value else: while 1: _s = yield _y try: _y = _i.send(_s) except StopIteration as _e: _r = _e.value break RESULT = _r
但是,現實情況要復雜一些,因為要處理客戶對 throw(…) 和 close() 方法的調用,而這兩個方法執行的操作必須傳入子生成器。此外,子生成器可能隻是純粹的迭代器,不支持 throw(…) 和 close() 方法,因此 yield from 結構的邏輯必須處理這種情況。如果子生成器實現瞭這兩個方法,而在子生成器內部,這兩個方法都會觸發異常拋出,這種情況也必須由 yield from 機制處理。調用方可能會無緣無故地讓子生成器自己拋出異常,實現 yield from 結構時也必須處理這種情況。最後,為瞭優化,如果調用方調用 next(…) 函數或 .send(None) 方法,都要轉交職責,在子生成器上調用next(…) 函數;僅當調用方發送的值不是 None 時,才使用子生成器的 .send(…) 方法。
下面的偽代碼,是考慮瞭上述情況之後,語句:RESULT = yield from EXPR的等效代碼:
_i = iter(EXPR) try: _y = next(_i) except StopIteration as _e: _r = _e.value else: while 1: try: _s = yield _y except GeneratorExit as _e: try: _m = _i.close except AttributeError: pass else: _m() raise _e except BaseException as _e: _x = sys.exc_info() try: _m = _i.throw except AttributeError: raise _e else: try: _y = _m(*_x) except StopIteration as _e: _r = _e.value break else: try: if _s is None: _y = next(_i) else: _y = _i.send(_s) except StopIteration as _e: _r = _e.value break RESULT = _r
上面的偽代碼中,會預激子生成器。這表明,用於自動預激的裝飾器與 yield from 結構不兼容。
三、greenlet的使用
python中為實現協程封裝瞭一些非常好用的包,首先介紹greenlet的使用。
Greenlet是python的一個C擴展,旨在提供可自行調度的‘微線程’, 即協程。generator實現的協程在yield value時隻能將value返回給調用者(caller)。 而在greenlet中,target.switch(value)可以切換到指定的協程(target), 然後yield value。greenlet用switch來表示協程的切換,從一個協程切換到另一個協程需要顯式指定。
以下例子:
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
當創建一個greenlet時,首先初始化一個空的棧, switch到這個棧的時候,會運行在greenlet構造時傳入的函數(首先在test1中打印 12), 如果在這個函數(test1)中switch到其他協程(到瞭test2 打印34),那麼該協程會被掛起,等到切換回來(在test2中切換回來 打印34)。當這個協程對應函數執行完畢,那麼這個協程就變成dead狀態。
對於greenlet,最常用的寫法是 x = gr.switch(y)。 這句話的意思是切換到gr,傳入參數y。當從其他協程(不一定是這個gr)切換回來的時候,將值付給x。
import greenlet def test1(x, y): z = gr2.switch(x+y) print 'test1 ', z def test2(u): print 'test2 ', u gr1.switch(10) gr1 = greenlet.greenlet(test1) gr2 = greenlet.greenlet(test2) print gr1.switch("hello", " world") --------------------------- >>> 'test2 ' 'hello world' >>> 'test1 ' 10 >>> None
上面的例子,第12行從main greenlet切換到瞭gr1,test1第3行切換到瞭gs2,然後gr1掛起,第8行從gr2切回gr1時,將值(10)返回值給瞭 z。
使用greenlet需要註意一下三點:
- 第一:greenlet創生之後,一定要結束,不能switch出去就不回來瞭,否則容易造成內存泄露
- 第二:python中每個線程都有自己的main greenlet及其對應的sub-greenlet ,不能線程之間的greenlet是不能相互切換的
- 第三:不能存在循環引用,這個是官方文檔明確說明
四、gevent的使用
gevent可以自動捕獲I/O耗時操作,來自動切換協程任務。
import gevent def 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是交叉打印信息的,因為在代碼執行的過程中,我們人為使用gevent.sleep(0)創建瞭一個阻塞,gevent在運行到這裡時就會自動切換函數切換函數。也可以在執行的時候sleep更長時間,可以發現兩個函數基本是同時運行然後各自等待。
關於協程,首先要充分理解協程的實現原理,然後使用現有的輪子greenlet和gevent時才能更加得心應手!
以上就是深入理解python協程的詳細內容,更多關於python協程的資料請關註WalkonNet其它相關文章!