您好,登錄后才能下訂單哦!
協程,又稱微線程、纖程,英文名Coroutine;用一句話說明什么是線程的話:協程是一種用戶態的輕量級線程。
Python對于協程的支持在python2中還比較簡單,但是也有可以使用的第三方庫,在python3中開始全面支持,也成為python3的一個核心功能,很值得學習。
協程,又稱微線程、纖程,英文名Coroutine;用一句話說明什么是線程的話:協程是一種用戶態的輕量級線程。
協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。因此:協程能保留上一次調用時的狀態(即所有局部狀態的一個特定組合),每次過程重入時,就相當于進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。
協程的優點:
1)無需線程上下文切換的開銷
2)無需原子操作鎖定及同步的開銷
3)方便切換控制流,簡化編程模型
4)高并發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。所以很適合用于高并發處理。
協程的缺點:
1)無法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程需要和進程配合才能運行在多CPU上
2)進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序
yield關鍵字
Python2對于協程的支持,是通過yield關鍵字實現的,下面示例代碼是一個常見的生產者—消費者模型,代碼示例如下:
def consumer():
r = ''
while True:
n = yield r
if not n:
continue
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK'
def produce(c):
c.next()
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()
if __name__ == '__main__':
c = consumer()
produce(c)
執行結果:
注意到consumer函數是一個generator(生成器),把一個consumer傳入produce后:
1)首先調用c.next()啟動生成器;
2)然后,一旦生產了東西,通過c.send(n)切換到consumer執行;
3)consumer通過yield拿到消息,處理,又通過yield把結果傳回;
4)produce拿到consumer處理的結果,繼續生產下一條消息;
5)produce決定不生產了,通過c.close()關閉consumer,整個過程結束。
整個流程無鎖,由一個線程執行,produce和consumer協作完成任務,所以稱為“協程”,而非線程的搶占式多任務。
傳統的生產者-消費者模型是一個線程寫消息,一個線程取消息,通過鎖機制控制隊列和等待,但一不小心就可能死鎖。
如果改用協程,生產者生產消息后,直接通過yield跳轉到消費者開始執行,待消費者執行完畢后,切換回生產者繼續生產,效率極高。
Python對協程的支持還非常有限,用在generator中的yield可以一定程度上實現協程。雖然支持不完全,但已經可以發揮相當大的威力了。
gevent模塊
Python通過yield提供了對協程的基本支持,但是不完全。而第三方的gevent為Python提供了比較完善的協程支持。gevent是第三方庫,通過greenlet實現協程,其基本思想是:
當一個greenlet遇到IO操作時,比如訪問網絡,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。由于IO操作非常耗時,經常使程序處于等待狀態,有了gevent為我們自動切換協程,就保證總有greenlet在運行,而不是等待IO。由于切換是在IO操作時自動完成,所以gevent需要修改Python自帶的一些標準庫,這一過程在啟動時通過monkey patch完成。
示例代碼如下:
from gevent import monkey; monkey.patch_all()
import gevent
import urllib2
def f(url):
print('GET: %s' % url)
resp = urllib2.urlopen(url)
data = resp.read()
print('%d bytes received from %s.' % (len(data), url))
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
執行結果:
從執行結果可以看到,網站訪問的順序是自動切換的。
gevent優缺
使用gevent,可以獲得極高的并發性能,但gevent只能在Unix/Linux下運行,在Windows下不保證正常安裝和運行。Python創始人Gvanrossum從來不喜歡Gevent,而是更愿意另辟蹊徑的實現asyncio(python3中的異步實現)。
1)Monkey-patching。中文「猴子補丁」,常用于對測試環境做一些hack。Gvanrossum說用它就是”patch-and-pray”,由于Gevent直接修改標準庫里面大部分的阻塞式系統調用,包括socket、ssl、threading和 select等模塊,而變為協作式運行。但是無法保證在復雜的生產環境中有哪些地方使用這些標準庫會由于打了補丁而出現奇怪的問題,那么只能祈禱(pray)了。
2)其次,在Python之禪中明確說過:「Explicit is better than implicit.」,猴子補丁明顯的背離了這個原則。
3)第三方庫支持。得確保項目中用到其他用到的網絡庫也必須使用純Python或者明確說明支持Gevent,而且就算有這樣的第三方庫,也需要擔心這個第三方庫的代碼質量和功能性。
4)Greenlet不支持Jython和IronPython,這樣就無法把gevent設計成一個標準庫了。
之前是沒有選擇,很多人選擇了Gevent,而現在明確的有了更正統的、正確的選擇:asyncio(下一節會介紹)。所以建議大家了解Gevent,擁抱asyncio。
另外,如果知道現在以及未來使用Gevent不會給項目造成困擾,那么用Gevent也是可以的。
Gvanrossum希望在Python 3 實現一個原生的基于生成器的協程庫,其中直接內置了對異步IO的支持,這就是asyncio,它在Python 3.4被引入到標準庫。
下面將簡單介紹asyncio的使用:
1)event_loop 事件循環:程序開啟一個無限的循環,程序員會把一些函數注冊到事件循環上。當滿足事件發生的時候,調用相應的協程函數。
2)coroutine 協程:協程對象,指一個使用async關鍵字定義的函數,它的調用不會立即執行函數,而是會返回一個協程對象。協程對象需要注冊到事件循環,由事件循環調用。
3)task 任務:一個協程對象就是一個原生可以掛起的函數,任務則是對協程進一步封裝,其中包含任務的各種狀態。
4)future: 代表將來執行或沒有執行的任務的結果。它和task上沒有本質的區別
5)async/await 關鍵字:python3.5 用于定義協程的關鍵字,async定義一個協程,await用于掛起阻塞的異步調用接口。
代碼示例如下:
import asyncio
import time
now = lambda: time.time()
async def do_some_work(x):
print('Waiting: {}s'.format(x))
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
async def main():
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(5)
coroutine3 = do_some_work(3)
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
done, pending = await asyncio.wait(tasks)
for task in done:
print('Task ret: ', task.result())
start = now()
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(main())
try:
loop.run_until_complete(task)
print('TIME: ', now() - start)
except KeyboardInterrupt as e:
print(asyncio.Task.all_tasks())
print(asyncio.gather(*asyncio.Task.all_tasks()).cancel())
loop.stop()
loop.run_forever()
finally:
loop.close()
執行結果:
可以看到程序執行時間是以等待時間最長的為準。
使用async可以定義協程對象,使用await可以針對耗時的操作進行掛起,就像生成器里的yield一樣,函數讓出控制權。協程遇到await,事件循環將會掛起該協程,執行別的協程,直到其他的協程也掛起或者執行完畢,再進行下一個協程的執行。耗時的操作一般是一些IO操作,例如網絡請求,文件讀取等。我們使用asyncio.sleep函數來模擬IO操作。協程的目的也是讓這些IO操作異步化。
Asyncio是python3中一個強大的內置庫,上述只是簡單的介紹了asyncio的用法有興趣的話,很值得去學習一下!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。