前言
前面的文章提到過,python使用多線程,會因?yàn)镚IL的原因?qū)е露嗑€程的使用效率低下,甚至比單個線程的處理速度還慢。然而在python編程中, 為了解決多線程之間上下文切換的開銷,以及增加線程控制的靈活性,python引入了協(xié)程 。本文我們就來說一說python協(xié)程的特點(diǎn)和使用方法。
?
一、協(xié)程定義
定義:協(xié)程(Coroutine),又稱微線程。協(xié)程的作用,是在執(zhí)行函數(shù)A時,可以隨時中斷,去執(zhí)行函數(shù)B,然后中斷繼續(xù)執(zhí)行函數(shù)A(可以自由切換)。但這一過程并不是函數(shù)調(diào)用(沒有調(diào)用語句),這一整個過程看似像多線程,然而協(xié)程只有一個線程執(zhí)行。
無論多線程和多進(jìn)程,IO的調(diào)度更多取決于系統(tǒng),而 協(xié)程的方式,調(diào)度來自用戶 ,用戶可以在函數(shù)中yield一個狀態(tài)。使用協(xié)程可以實(shí)現(xiàn)高效的并發(fā)任務(wù)。
?
二、協(xié)程的實(shí)現(xiàn)
Python的在3.4中引入了協(xié)程的概念,可是這個還是以生成器對象為基礎(chǔ),3.5則確定了協(xié)程的語法。
實(shí)現(xiàn)協(xié)程通常可以引入一些python庫來實(shí)現(xiàn),其中最常用的就是asyncio,當(dāng)然也有一些其他的如tornado和gevent都實(shí)現(xiàn)了類似的功能。這里我們就來了解一下asyncio
協(xié)程通過 async/await 語法進(jìn)行聲明 ,是編寫異步應(yīng)用的推薦方式。我們先來了解asyncio的幾個概念名詞:
event_loop 事件循環(huán):程序開啟一個無限的循環(huán),程序員會把一些函數(shù)注冊到事件循環(huán)上。當(dāng)滿足事件發(fā)生的時候,調(diào)用相應(yīng)的協(xié)程函數(shù)。
coroutine 協(xié)程:協(xié)程對象,指一個使用async關(guān)鍵字定義的函數(shù),它的調(diào)用不會立即執(zhí)行函數(shù),而是會返回一個協(xié)程對象。協(xié)程對象需要注冊到事件循環(huán),由事件循環(huán)調(diào)用。
task 任務(wù):一個協(xié)程對象就是一個原生可以掛起的函數(shù),任務(wù)則是對協(xié)程進(jìn)一步封裝,其中包含任務(wù)的各種狀態(tài)。
future: 代表將來執(zhí)行或沒有執(zhí)行的任務(wù)的結(jié)果。它和task上沒有本質(zhì)的區(qū)別
async/await 關(guān)鍵字:python3.5 用于定義協(xié)程的關(guān)鍵字,async定義一個協(xié)程,await用于掛起阻塞的異步調(diào)用接口。
為了解釋上面的名詞,我們一步步來實(shí)現(xiàn)簡單的異步編程:
1、定義一個協(xié)程
import time
import asyncio
# 定義一個協(xié)程并創(chuàng)建一個事件循環(huán)
loop = asyncio.get_event_loop()
# 協(xié)程對象coroutine需要在事件循環(huán)里面才能執(zhí)行
loop.run_until_complete(coroutine)
2、協(xié)程對象
async def sync_func(x):
print('Waiting: ', x)
# 實(shí)例化一個協(xié)程對象
coroutine = sync_func(2)
由上面可以得出, 用async修飾的方法是一個協(xié)程對象,協(xié)程對象需要在協(xié)程的事件循環(huán)里面才能運(yùn)行 。
3、創(chuàng)建一個task
task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)
事件循環(huán)除了可以運(yùn)行協(xié)程對象,也可以運(yùn)行任務(wù)。
在這里 task只是對協(xié)程對象coroutine進(jìn)行了封裝,task還可以綁定協(xié)程對象執(zhí)行后的回調(diào),甚至還可以封裝成list數(shù)組等 ,逐個進(jìn)行事件循環(huán)調(diào)用。
4、task綁定回調(diào)函數(shù)
def callback(x):
print(x)
task = asyncio.ensure_future(coroutine)
task.add_done_callback(functools.partial(callback, 2))
loop.run_until_complete(task)
其中callback是回調(diào)函數(shù),執(zhí)行完協(xié)程之后會執(zhí)行回調(diào)函數(shù)。綁定回調(diào)也是用task封裝協(xié)程對象的一個好處
5、協(xié)程的阻塞和await
async def sync_func(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
coroutine = sync_func(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)
如上所述,如果協(xié)程對象函數(shù)里面帶有 await ,是可以實(shí)現(xiàn)協(xié)程的運(yùn)行阻塞的。上面例子執(zhí)行結(jié)果是print之后sleep了2秒,這 期間可以讓出控制器,loop可以去調(diào)用別的協(xié)程 ,然后再回來執(zhí)行return,return的結(jié)果可以用task.result()來獲取。
6、多個協(xié)程并發(fā)執(zhí)行
async def sync_func(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
start = now()
coroutine1 = sync_func(1)
coroutine2 = sync_func(2)
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2)
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
上述例子執(zhí)行結(jié)果為:協(xié)程1print之后阻塞,再協(xié)程2print后也阻塞,最后協(xié)程1return,協(xié)程2再return。
7、協(xié)程停止:啟動事件循環(huán)之后,馬上ctrl+c,會觸發(fā)run_until_complete的執(zhí)行異常 KeyBorardInterrupt。然后通過循環(huán)asyncio.Task取消future。
try:
loop.run_until_complete(asyncio.wait(tasks))
except KeyboardInterrupt as e:
loop.stop()
loop.run_forever()
finally:
loop.close()
?
三、協(xié)程的使用場景
1、當(dāng)一個程序變得很大而且復(fù)雜時,將其劃分為子程序,每一部分實(shí)現(xiàn)特定的任務(wù)是個不錯的方案。 子程序不能單獨(dú)執(zhí)行,只能在主程序的請求下執(zhí)行,主程序負(fù)責(zé)協(xié)調(diào)使用各個子程序 。協(xié)程就是子程序的泛化。和子程序一樣的事,協(xié)程只負(fù)責(zé)計(jì)算任務(wù)的一步;和子程序不一樣的是,協(xié)程沒有主程序來進(jìn)行調(diào)度。
2、異步爬蟲
很多關(guān)心協(xié)程的朋友,大部分是用其寫爬蟲,這是因?yàn)閰f(xié)程能很好的解決IO阻塞問題。那么對于異步爬蟲的需求,使用協(xié)程的方法大致如下:
(1)grequests;
(2)爬蟲模塊+gevent;
(3)aiohttp;
(4)scrapy框架+asyncio模塊
3、協(xié)程池
類似于gevent,我們可以先創(chuàng)建好協(xié)程,放入一個協(xié)程池中,每次有任務(wù)請求的時候都由協(xié)程去執(zhí)行,主線程進(jìn)行統(tǒng)一管理和調(diào)度,這在一下I/O密集型的數(shù)據(jù)清洗等工作中可以提高很多效率。
?
四、協(xié)程的優(yōu)缺點(diǎn)
1、執(zhí)行效率高,因?yàn)樽映绦蚯袚Q(函數(shù))不是線程切換,由程序自身控制,沒有切換線程的開銷。所以與多線程相比,線程的數(shù)量越多,協(xié)程性能的優(yōu)勢越明顯;
2、不需要多線程的鎖機(jī)制,因?yàn)橹挥幸粋€線程,也不存在同時寫變量沖突,在控制共享資源時也不需要加鎖,因此執(zhí)行效率高很多。
3、缺點(diǎn): 無法利用多核資源 ,協(xié)程的本質(zhì)是個單線程,它不能同時將 單個CPU 的多個核用上,協(xié)程需要和進(jìn)程配合才能運(yùn)行在多CPU上。
4、 進(jìn)行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序
參考鏈接:
https://thief.one/2017/02/20/Python%E5%8D%8F%E7%A8%8B/
https://www.jianshu.com/p/7690edfe9ba5
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

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