python ?generator與coroutine
?
協(xié)程
簡單介紹
協(xié)程,又稱微線程,纖程,英文名Coroutine。
協(xié)程是一種用戶態(tài)的輕量級線程,又稱微線程。
協(xié)程擁有自己的寄存器上下文和棧,調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。因此:協(xié)程能保留上一次調用時的狀態(tài)(即所有局部狀態(tài)的一個特定組合),每次過程重入時就相當于進入上一次調用的狀態(tài),換種說法:進入上一次離開時所處邏輯流的位置。
?
優(yōu)缺點
優(yōu)點:
1.無需線程上下文切換的開銷
2.無需原子操作鎖定及同步的開銷
3.方便切換控制流,簡化編程模型
4.高并發(fā)+高擴展性+低成本:一個CPU支持上萬的協(xié)程都不是問題。所以很適合用于高并發(fā)處理。
所謂原子操作是指不會被線程調度機制打斷的操作;這種操作一旦開始,就一直運行到結束,中間不會有任何 context switch (切換到另一個線程)。
原子操作可以是一個步驟,也可以是多個操作步驟,但是其順序是不可以被打亂,或者切割掉只執(zhí)行部分。視作整體是原子性的核心。
缺點:
1.無法利用多核資源:協(xié)程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協(xié)程需要和進程配合才能運行在多CPU上.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
但是多進程+協(xié)程,可以充分利用多核,又充分發(fā)揮協(xié)程的高效率,可獲得極高的性能。
2.進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序。
?
協(xié)程可以用在哪些場景呢
可以歸納為非阻塞等待的場景,如游戲編程,異步IO,事件驅動。
?
協(xié)程詳解
Python對協(xié)程的支持是通過generator(生成器)實現(xiàn)的。
要理解生成器,我們先要理解迭代器。什么是迭代器?
在python中一個可以迭代的數(shù)據(jù)調用iter方法,就可以得到一個迭代器,這個迭代器一定具有next方法,在調用這個迭代器的next方法時,迭代器就回返回它的下一個值,當?shù)髦袥]有值可以返回了,就回拋出一個名為StopIteration的異常,停止迭代。
什么是生成器?
?生成器是一個特殊的程序,可以被用作控制循環(huán)的迭代行為,python中生成器是迭代器的一種,使用yield返回值函數(shù),每次調用yield會暫停,而可以使用next()函數(shù)和send()函數(shù)恢復生成器。
?生成器是個比較特殊的可迭代對象,它與其他的可迭代對象不太一樣的地方,其他的可迭代對象需要調用iter方法,返回個迭代器對象,然后通過迭代器對象去執(zhí)行next方法,獲取迭代器中的值,但是生成器直接可以被迭代,無需執(zhí)行iter方法。
至少我們現(xiàn)在要明白
1.帶有 yield 的函數(shù)不再是一個普通函數(shù),而是一個生成器generator.
2.生成器是可以迭代的,使用next()方法.
3.生成器(generator)能夠迭代的關鍵是它有一個next()方法,工作原理就是通過重復調用next()方法,直到捕獲一個異常。
4.協(xié)程是通過生成器的實現(xiàn)的
5.協(xié)程有四個狀態(tài),如下:
?'GEN_CREATED' 等待開始執(zhí)行。
?'GEN_RUNNING' 解釋器正在執(zhí)行。
?'GEN_SUSPENDED' 在 yield 表達式處暫停。
?'GEN_CLOSED' 執(zhí)行結束。
?
第一個示例程序
test為生成器,即可以迭代,我們可以使用next方法迭代。
第11行創(chuàng)建生成器test,在python的函數(shù)(function)定義中,只要出現(xiàn)了yield表達式(Yield expression),那么事實上定義的是一個 generator function , 調用這個generator function返回值是一個 generator。
第12行打印生成器。
第14行我們可以了生成器的狀態(tài),結果為:GEN_CREATED。
第15行第一次執(zhí)行next方法,send和next操作都是調用生成器,而第一調用生成器就是啟動生成器,啟動生成器必須使用next()語句或是send(None)啟動生成器,不能使用send發(fā)送一個非None的值,例如send(None),程序運行到y(tǒng)ield 1,程序在這里暫停執(zhí)行,并返回1,我們要明白在這里yield有了return的功能,返回了1,而且還暫停了程序。
第16行我們再次打印生成器的狀態(tài),結果:GEN_SUSPENDED。
第17行第二次執(zhí)行next方法,程序運行到y(tǒng)ield 2,程序在這里暫停,并返回2。
現(xiàn)在就很清晰了,使用yield可以切出生成器,它還有return的功能,切進生成器可以使用next()方法。
調用生成器的next(),將運行到y(tǒng)ield位置,此時暫停執(zhí)行環(huán)境,并返回這條語句yield關鍵詞后面跟隨的值。?這是next()的使用方法。
1 import inspect 2 def func1(): 3 yield 1 4 print ( " 第一個yield執(zhí)行完成~ " ) 5 yield 2 6 print ( " 第二個yield執(zhí)行完成~ " ) 7 yield 3 8 print ( " 第三個yield執(zhí)行完成~ " ) 9 10 11 test = func1() 12 print (test) 13 14 print ( " 還未執(zhí)行next: " ,inspect.getgeneratorstate(test)) 15 next(test) 16 print ( " 第一次執(zhí)行next: " ,inspect.getgeneratorstate(test)) 17 next(test) 18 print ( " 第二次執(zhí)行next: " ,inspect.getgeneratorstate(test)) 19 next(test) 20 print ( " 第三次執(zhí)行next: " ,inspect.getgeneratorstate(test)) 21 next(test) 22 print ( " 第四次執(zhí)行next: " ,inspect.getgeneratorstate(test))
?
運行結果:
Traceback (most recent call last): 還未執(zhí)行next: GEN_CREATED 第一次執(zhí)行next: GEN_SUSPENDED 第一個yield執(zhí)行完成 ~ 第二次執(zhí)行next: GEN_SUSPENDED 第二個yield執(zhí)行完成 ~ File " C:/Pycham/異步編程/test3.py " , line 21, in 第三次執(zhí)行next: GEN_SUSPENDED 第三個yield執(zhí)行完成 ~ next(test) StopIteration
?
?
第二個示例程序
在上面的列子我們使用next()切進了生成器,但是每次切換進生成器,都沒有傳入?yún)?shù),接下來將介紹send()方法,send()方法不僅可以切進生成器,而且還可以攜帶參數(shù)。
除了next和send方法,generator還提供了兩個實用的方法,throw和close,這兩個方法加強了caller對generator的控制。send方法可以傳遞一個值給generator,throw方法在generator掛起的地方拋出異常,close方法讓generator正常結束(之后就不能再調用next send了)。
1 import sys 2 def func2(a): 3 print ( ' -> Started: a = ' , a) 4 b = yield a 5 print ( ' -> Received: b = ' , b) 6 c = yield a + b 7 print ( ' -> Received: c = ' , c) 8 9 test = func2(2 ) 10 value = next(test) # 協(xié)程執(zhí)行到`b = yield a`處暫停,等待為b賦值,并返回a 11 print (value) 12 value = test.send(88) # 協(xié)程執(zhí)行到`c = yield a + b`處暫停,等待為c賦值,并返回a + b 13 print (value) 14 try : 15 test.send(11 ) 16 except StopIteration: 17 sys.exit(0)
運行結果:
-> Started: a = 2 2 -> Received: b = 88 90 -> Received: c = 11
?
?
第三個示例程序
傳統(tǒng)的生產(chǎn)者-消費者模型是一個線程寫消息,一個線程取消息,通過鎖機制控制隊列和等待,但一不小心就可能死鎖。如果改用協(xié)程,生產(chǎn)者生產(chǎn)消息后,直接通過yield跳轉到消費者開始執(zhí)行,待消費者執(zhí)行完畢后,切換回生產(chǎn)者繼續(xù)生產(chǎn),效率極高:
1 import time 2 3 def cumtom(name): 4 print ( ' %s準備吃包子 ' % name) 5 time.sleep(1 ) 6 while 1 : 7 count= yield 8 print ( ' %s吃到第%d個包子 ' % (name,count)) 9 10 def producter(): 11 next(con1) 12 next(con2) 13 # con1.__next__() 14 # con2.__next__() 15 n=1 16 while 1 : 17 time.sleep(1 ) 18 print ( ' 已經(jīng)生產(chǎn)出來%d、%d個包子 ' %(n,n+1 )) 19 # 通過send方法通知 20 con1.send(n) 21 con2.send(n+1 ) 22 n+=2 23 24 # cumtom函數(shù)里面有yield,這里傳遞參數(shù),會創(chuàng)建一個生成器對象,(提前做了預處理) 25 con1=cumtom( ' cumtom1 ' ) 26 con2=cumtom( ' cumtom2 ' ) 27 producter()
?
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
