您好,登錄后才能下訂單哦!
協(xié)程,又稱微線程、纖程,英文名Coroutine;用一句話說明什么是線程的話:協(xié)程是一種用戶態(tài)的輕量級線程。
Python對于協(xié)程的支持在python2中還比較簡單,但是也有可以使用的第三方庫,在python3中開始全面支持,也成為python3的一個(gè)核心功能,很值得學(xué)習(xí)。
協(xié)程,又稱微線程、纖程,英文名Coroutine;用一句話說明什么是線程的話:協(xié)程是一種用戶態(tài)的輕量級線程。
協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時(shí),將寄存器上下文和棧保存到其他地方,在切回來的時(shí)候,恢復(fù)先前保存的寄存器上下文和棧。因此:協(xié)程能保留上一次調(diào)用時(shí)的狀態(tài)(即所有局部狀態(tài)的一個(gè)特定組合),每次過程重入時(shí),就相當(dāng)于進(jìn)入上一次調(diào)用的狀態(tài),換種說法:進(jìn)入上一次離開時(shí)所處邏輯流的位置。
協(xié)程的優(yōu)點(diǎn):
1)無需線程上下文切換的開銷
2)無需原子操作鎖定及同步的開銷
3)方便切換控制流,簡化編程模型
4)高并發(fā)+高擴(kuò)展性+低成本:一個(gè)CPU支持上萬的協(xié)程都不是問題。所以很適合用于高并發(fā)處理。
協(xié)程的缺點(diǎn):
1)無法利用多核資源:協(xié)程的本質(zhì)是個(gè)單線程,它不能同時(shí)將 單個(gè)CPU 的多個(gè)核用上,協(xié)程需要和進(jìn)程配合才能運(yùn)行在多CPU上
2)進(jìn)行阻塞(Blocking)操作(如IO時(shí))會(huì)阻塞掉整個(gè)程序
yield關(guān)鍵字
Python2對于協(xié)程的支持,是通過yield關(guān)鍵字實(shí)現(xiàn)的,下面示例代碼是一個(gè)常見的生產(chǎn)者—消費(fèi)者模型,代碼示例如下:
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)
執(zhí)行結(jié)果:
注意到consumer函數(shù)是一個(gè)generator(生成器),把一個(gè)consumer傳入produce后:
1)首先調(diào)用c.next()啟動(dòng)生成器;
2)然后,一旦生產(chǎn)了東西,通過c.send(n)切換到consumer執(zhí)行;
3)consumer通過yield拿到消息,處理,又通過yield把結(jié)果傳回;
4)produce拿到consumer處理的結(jié)果,繼續(xù)生產(chǎn)下一條消息;
5)produce決定不生產(chǎn)了,通過c.close()關(guān)閉consumer,整個(gè)過程結(jié)束。
整個(gè)流程無鎖,由一個(gè)線程執(zhí)行,produce和consumer協(xié)作完成任務(wù),所以稱為“協(xié)程”,而非線程的搶占式多任務(wù)。
傳統(tǒng)的生產(chǎn)者-消費(fèi)者模型是一個(gè)線程寫消息,一個(gè)線程取消息,通過鎖機(jī)制控制隊(duì)列和等待,但一不小心就可能死鎖。
如果改用協(xié)程,生產(chǎn)者生產(chǎn)消息后,直接通過yield跳轉(zhuǎn)到消費(fèi)者開始執(zhí)行,待消費(fèi)者執(zhí)行完畢后,切換回生產(chǎn)者繼續(xù)生產(chǎn),效率極高。
Python對協(xié)程的支持還非常有限,用在generator中的yield可以一定程度上實(shí)現(xiàn)協(xié)程。雖然支持不完全,但已經(jīng)可以發(fā)揮相當(dāng)大的威力了。
gevent模塊
Python通過yield提供了對協(xié)程的基本支持,但是不完全。而第三方的gevent為Python提供了比較完善的協(xié)程支持。gevent是第三方庫,通過greenlet實(shí)現(xiàn)協(xié)程,其基本思想是:
當(dāng)一個(gè)greenlet遇到IO操作時(shí),比如訪問網(wǎng)絡(luò),就自動(dòng)切換到其他的greenlet,等到IO操作完成,再在適當(dāng)?shù)臅r(shí)候切換回來繼續(xù)執(zhí)行。由于IO操作非常耗時(shí),經(jīng)常使程序處于等待狀態(tài),有了gevent為我們自動(dòng)切換協(xié)程,就保證總有g(shù)reenlet在運(yùn)行,而不是等待IO。由于切換是在IO操作時(shí)自動(dòng)完成,所以gevent需要修改Python自帶的一些標(biāo)準(zhǔn)庫,這一過程在啟動(dòng)時(shí)通過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/'),
])
執(zhí)行結(jié)果:
從執(zhí)行結(jié)果可以看到,網(wǎng)站訪問的順序是自動(dòng)切換的。
gevent優(yōu)缺
使用gevent,可以獲得極高的并發(fā)性能,但gevent只能在Unix/Linux下運(yùn)行,在Windows下不保證正常安裝和運(yùn)行。Python創(chuàng)始人Gvanrossum從來不喜歡Gevent,而是更愿意另辟蹊徑的實(shí)現(xiàn)asyncio(python3中的異步實(shí)現(xiàn))。
1)Monkey-patching。中文「猴子補(bǔ)丁」,常用于對測試環(huán)境做一些hack。Gvanrossum說用它就是”patch-and-pray”,由于Gevent直接修改標(biāo)準(zhǔn)庫里面大部分的阻塞式系統(tǒng)調(diào)用,包括socket、ssl、threading和 select等模塊,而變?yōu)閰f(xié)作式運(yùn)行。但是無法保證在復(fù)雜的生產(chǎn)環(huán)境中有哪些地方使用這些標(biāo)準(zhǔn)庫會(huì)由于打了補(bǔ)丁而出現(xiàn)奇怪的問題,那么只能祈禱(pray)了。
2)其次,在Python之禪中明確說過:「Explicit is better than implicit.」,猴子補(bǔ)丁明顯的背離了這個(gè)原則。
3)第三方庫支持。得確保項(xiàng)目中用到其他用到的網(wǎng)絡(luò)庫也必須使用純Python或者明確說明支持Gevent,而且就算有這樣的第三方庫,也需要擔(dān)心這個(gè)第三方庫的代碼質(zhì)量和功能性。
4)Greenlet不支持Jython和IronPython,這樣就無法把gevent設(shè)計(jì)成一個(gè)標(biāo)準(zhǔn)庫了。
之前是沒有選擇,很多人選擇了Gevent,而現(xiàn)在明確的有了更正統(tǒng)的、正確的選擇:asyncio(下一節(jié)會(huì)介紹)。所以建議大家了解Gevent,擁抱asyncio。
另外,如果知道現(xiàn)在以及未來使用Gevent不會(huì)給項(xiàng)目造成困擾,那么用Gevent也是可以的。
Gvanrossum希望在Python 3 實(shí)現(xiàn)一個(gè)原生的基于生成器的協(xié)程庫,其中直接內(nèi)置了對異步IO的支持,這就是asyncio,它在Python 3.4被引入到標(biāo)準(zhǔn)庫。
下面將簡單介紹asyncio的使用:
1)event_loop 事件循環(huán):程序開啟一個(gè)無限的循環(huán),程序員會(huì)把一些函數(shù)注冊到事件循環(huán)上。當(dāng)滿足事件發(fā)生的時(shí)候,調(diào)用相應(yīng)的協(xié)程函數(shù)。
2)coroutine 協(xié)程:協(xié)程對象,指一個(gè)使用async關(guān)鍵字定義的函數(shù),它的調(diào)用不會(huì)立即執(zhí)行函數(shù),而是會(huì)返回一個(gè)協(xié)程對象。協(xié)程對象需要注冊到事件循環(huán),由事件循環(huán)調(diào)用。
3)task 任務(wù):一個(gè)協(xié)程對象就是一個(gè)原生可以掛起的函數(shù),任務(wù)則是對協(xié)程進(jìn)一步封裝,其中包含任務(wù)的各種狀態(tài)。
4)future: 代表將來執(zhí)行或沒有執(zhí)行的任務(wù)的結(jié)果。它和task上沒有本質(zhì)的區(qū)別
5)async/await 關(guān)鍵字:python3.5 用于定義協(xié)程的關(guān)鍵字,async定義一個(gè)協(xié)程,await用于掛起阻塞的異步調(diào)用接口。
代碼示例如下:
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()
執(zhí)行結(jié)果:
可以看到程序執(zhí)行時(shí)間是以等待時(shí)間最長的為準(zhǔn)。
使用async可以定義協(xié)程對象,使用await可以針對耗時(shí)的操作進(jìn)行掛起,就像生成器里的yield一樣,函數(shù)讓出控制權(quán)。協(xié)程遇到await,事件循環(huán)將會(huì)掛起該協(xié)程,執(zhí)行別的協(xié)程,直到其他的協(xié)程也掛起或者執(zhí)行完畢,再進(jìn)行下一個(gè)協(xié)程的執(zhí)行。耗時(shí)的操作一般是一些IO操作,例如網(wǎng)絡(luò)請求,文件讀取等。我們使用asyncio.sleep函數(shù)來模擬IO操作。協(xié)程的目的也是讓這些IO操作異步化。
Asyncio是python3中一個(gè)強(qiáng)大的內(nèi)置庫,上述只是簡單的介紹了asyncio的用法有興趣的話,很值得去學(xué)習(xí)一下!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。