摘要:創(chuàng)建第一個(gè)協(xié)程推薦使用語法來聲明協(xié)程,來編寫異步應(yīng)用程序。協(xié)程兩個(gè)緊密相關(guān)的概念是協(xié)程函數(shù)通過定義的函數(shù)協(xié)程對象調(diào)用協(xié)程函數(shù)返回的對象。它是一個(gè)低層級的可等待對象,表示一個(gè)異步操作的最終結(jié)果。
我們講以Python 3.7 上的asyncio為例講解如何使用Python的異步IO。
創(chuàng)建第一個(gè)協(xié)程Python 3.7 推薦使用 async/await 語法來聲明協(xié)程,來編寫異步應(yīng)用程序。我們來創(chuàng)建第一個(gè)協(xié)程函數(shù):首先打印一行“你好”,等待1秒鐘后再打印“猿人學(xué)”。
sayhi()函數(shù)通過 async 聲明為協(xié)程函數(shù),較之前的修飾器聲明更簡潔明了。
在實(shí)踐過程中,什么功能的函數(shù)要用async聲明為協(xié)程函數(shù)呢?就是那些能發(fā)揮異步IO性能的函數(shù),比如讀寫文件、讀寫網(wǎng)絡(luò)、讀寫數(shù)據(jù)庫,這些都是浪費(fèi)時(shí)間的IO操作,把它們協(xié)程化、異步化從而提高程序的整體效率(速度)。
sayhi()函數(shù)是通過?asyncio.run()來運(yùn)行的,而不是直接調(diào)用這個(gè)函數(shù)(協(xié)程)。因?yàn)?,直接調(diào)用并不會把它加入調(diào)度日程,而只是簡單的返回一個(gè)協(xié)程對象:
那么,如何真正運(yùn)行一個(gè)協(xié)程呢?asyncio 提供了三種機(jī)制:
(1)asyncio.run() 函數(shù),這是異步程序的主入口,相當(dāng)于C語言中的main函數(shù)。
(2)用await等待協(xié)程,比如上例中的?await asyncio.sleep(1)?。再看下面的例子,我們定義了協(xié)程?say_delay()?,在main()協(xié)程中調(diào)用兩次,第一次延遲1秒后打印“你好”,第二次延遲2秒后打印“猿人學(xué)”。這樣我們通過 await 運(yùn)行了兩個(gè)協(xié)程。
從起止時(shí)間可以看出,兩個(gè)協(xié)程是順序執(zhí)行的,總共耗時(shí)1+2=3秒。
(3)通過?asyncio.create_task()?函數(shù)并發(fā)運(yùn)行作為 asyncio 任務(wù)(Task) 的多個(gè)協(xié)程。下面,我們用create_task()來修改上面的main()協(xié)程,從而讓兩個(gè)say_delay()協(xié)程并發(fā)運(yùn)行:
從運(yùn)行結(jié)果的起止時(shí)間可以看出,兩個(gè)協(xié)程是并發(fā)執(zhí)行的了,總耗時(shí)等于最大耗時(shí)2秒。
asyncio.create_task()?是一個(gè)很有用的函數(shù),在爬蟲中它可以幫助我們實(shí)現(xiàn)大量并發(fā)去下載網(wǎng)頁。在Python 3.6中與它對應(yīng)的是?ensure_future()。
可等待對象(awaitables)可等待對象,就是可以在 await 表達(dá)式中使用的對象,前面我們已經(jīng)接觸了兩種可等待對象的類型:協(xié)程和任務(wù),還有一個(gè)是低層級的Future。
asyncio模塊的許多API都需要傳入可等待對象,比如 run(), create_task() 等等。
(1)協(xié)程協(xié)程是可等待對象,可以在其它協(xié)程中被等待。協(xié)程兩個(gè)緊密相關(guān)的概念是:
協(xié)程函數(shù):通過 async def 定義的函數(shù);
協(xié)程對象:調(diào)用協(xié)程函數(shù)返回的對象。
運(yùn)行上面這段程序,結(jié)果為:
co is now is 1548512708.2026224 now is 1548512708.202648
可以看到,直接運(yùn)行協(xié)程函數(shù) whattime()得到的co是一個(gè)協(xié)程對象,因?yàn)閰f(xié)程對象是可等待的,所以通過 await 得到真正的當(dāng)前時(shí)間。now2是直接await 協(xié)程函數(shù),也得到了當(dāng)前時(shí)間的返回值。
(2)任務(wù)前面我們講到,任務(wù)是用來調(diào)度協(xié)程的,以便并發(fā)執(zhí)行協(xié)程。當(dāng)一個(gè)協(xié)程通過?asyncio.create_task()?被打包為一個(gè) 任務(wù),該協(xié)程將自動(dòng)加入程序調(diào)度日程準(zhǔn)備立即運(yùn)行。
create_task()的基本使用前面例子已經(jīng)講過。它返回的task通過await來等待其運(yùn)行完。如果,我們不等待,會發(fā)生什么?“準(zhǔn)備立即運(yùn)行”又該如何理解呢?先看看下面這個(gè)例子:
運(yùn)行這段代碼的情況是這樣的:
首先,1秒鐘后打印一行,這是第13,14行代碼運(yùn)行的結(jié)果:
calling:0, now is 09:15:15
接著,停頓1秒后,連續(xù)打印4行:
calling:1, now is 09:15:16 calling:2, now is 09:15:16 calling:3, now is 09:15:16 calling:4, now is 09:15:16
從這個(gè)結(jié)果看,asyncio.create_task()產(chǎn)生的4個(gè)任務(wù),我們并沒有await,它們也執(zhí)行了。關(guān)鍵在于第18行的?await,如果把這一行去掉或是sleep的時(shí)間小于1秒(比whattime()里面的sleep時(shí)間少即可),就會只看到第一行的輸出結(jié)果而看不到后面四行的輸出。這是因?yàn)?,main()不sleep或sleep少于1秒鐘,main()就在whattime()還未來得及打印結(jié)果(因?yàn)?,它要sleep 1秒)就退出了,從而整個(gè)程序也退出了,就沒有whattime()的輸出結(jié)果。
再來理解一下“準(zhǔn)備立即執(zhí)行”這個(gè)說法。它的意思就是,create_task()只是打包了協(xié)程并加入調(diào)度隊(duì)列還未執(zhí)行,并準(zhǔn)備立即執(zhí)行,什么時(shí)候執(zhí)行呢?在“主協(xié)程”(調(diào)用create_task()的協(xié)程)掛起的時(shí)候,這里的“掛起”有兩個(gè)方式:
一是,通過 await task 來執(zhí)行這個(gè)任務(wù);
另一個(gè)是,主協(xié)程通過 await sleep 掛起,事件循環(huán)就去執(zhí)行task了。
我們知道,asyncio是通過事件循環(huán)實(shí)現(xiàn)異步的。在主協(xié)程 main()里面,沒有遇到 await 時(shí),事件就是執(zhí)行main()函數(shù),遇到 await 時(shí),事件循環(huán)就去執(zhí)行別的協(xié)程,即create_task()生成的whattime()的4個(gè)任務(wù),這些任務(wù)一開始就是 await sleep 1秒。這時(shí)候,主協(xié)程和4個(gè)任務(wù)協(xié)程都掛起了,CPU空閑,事件循環(huán)等待協(xié)程的消息。
如果main()協(xié)程只sleep了0.1秒,它就先醒了,給事件循環(huán)發(fā)消息,事件循環(huán)就來繼續(xù)執(zhí)行main()協(xié)程,而main()后面已經(jīng)沒有代碼,就退出該協(xié)程,退出它也就意味著整個(gè)程序退出,4個(gè)任務(wù)就沒機(jī)會打印結(jié)果;
如果main()協(xié)程sleep時(shí)間多余1秒,那么4個(gè)任務(wù)先喚醒,就會得到全部的打印結(jié)果;
如果main()的18行sleep等于1秒時(shí),和4個(gè)任務(wù)的sleep時(shí)間相同,也會得到全部打印結(jié)果。這是為什么呢?
我猜想是這樣的:4個(gè)任務(wù)生成在前,第18行的sleep在后,事件循環(huán)的消息響應(yīng)可能有個(gè)先進(jìn)先出的順序。后面深入asyncio的代碼專門研究一下這個(gè)猜想正確與否。
(3)Future它是一個(gè)低層級的可等待對象,表示一個(gè)異步操作的最終結(jié)果。目前,我們寫應(yīng)用程序還用不到它,暫不學(xué)習(xí)。
asyncio異步IO協(xié)程總結(jié)協(xié)程就是我們異步操作的片段。通常,寫程序都會把全部功能分成很多不同功能的函數(shù),目的是為了結(jié)構(gòu)清晰;進(jìn)一步,把那些涉及耗費(fèi)時(shí)間的IO操作(讀寫文件、數(shù)據(jù)庫、網(wǎng)絡(luò))的函數(shù)通過 async def 異步化,就是異步編程。
那些異步函數(shù)(協(xié)程函數(shù))都是通過消息機(jī)制被事件循環(huán)管理調(diào)度著,整個(gè)程序的執(zhí)行是單線程的,但是某個(gè)協(xié)程A進(jìn)行IO時(shí),事件循環(huán)就去執(zhí)行其它協(xié)程非IO的代碼。當(dāng)事件循環(huán)收到協(xié)程A結(jié)束IO的消息時(shí),就又回來執(zhí)行協(xié)程A,這樣事件循環(huán)不斷在協(xié)程之間轉(zhuǎn)換,充分利用了IO的閑置時(shí)間,從而并發(fā)的進(jìn)行多個(gè)IO操作,這就是異步IO。
寫異步IO程序時(shí)記住一個(gè)準(zhǔn)則:需要IO的地方異步。其它地方即使用了協(xié)程函數(shù)也是沒用的。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/43851.html
摘要:并發(fā)的方式有多種,多線程,多進(jìn)程,異步等。多線程和多進(jìn)程之間的場景切換和通訊代價(jià)很高,不適合密集型的場景關(guān)于多線程和多進(jìn)程的特點(diǎn)已經(jīng)超出本文討論的范疇,有興趣的同學(xué)可以自行搜索深入理解。 編程中,我們經(jīng)常會遇到并發(fā)這個(gè)概念,目的是讓軟件能充分利用硬件資源,提高性能。并發(fā)的方式有多種,多線程,多進(jìn)程,異步IO等。多線程和多進(jìn)程更多應(yīng)用于CPU密集型的場景,比如科學(xué)計(jì)算的時(shí)間都耗費(fèi)在CPU...
摘要:具有以下基本同步原語子進(jìn)程提供了通過創(chuàng)建和管理子進(jìn)程的。雖然隊(duì)列不是線程安全的,但它們被設(shè)計(jì)為專門用于代碼。表示異步操作的最終結(jié)果。 Python的asyncio是使用 async/await 語法編寫并發(fā)代碼的標(biāo)準(zhǔn)庫。通過上一節(jié)的講解,我們了解了它不斷變化的發(fā)展歷史。到了Python最新穩(wěn)定版 3.7 這個(gè)版本,asyncio又做了比較大的調(diào)整,把這個(gè)庫的API分為了 高層級API和...
摘要:理解迭代對象迭代器生成器后端掘金本文源自作者的一篇博文,原文是,俺寫的這篇文章是按照自己的理解做的參考翻譯。比較的是兩個(gè)對象的內(nèi)容是后端掘金黑魔法之協(xié)程異步后端掘金本文為作者原創(chuàng),轉(zhuǎn)載請先與作者聯(lián)系。 完全理解關(guān)鍵字with與上下文管理器 - 掘金如果你有閱讀源碼的習(xí)慣,可能會看到一些優(yōu)秀的代碼經(jīng)常出現(xiàn)帶有 with 關(guān)鍵字的語句,它通常用在什么場景呢?今天就來說說 with 和 上下...
摘要:以下這些項(xiàng)目,你拿來學(xué)習(xí)學(xué)習(xí)練練手。當(dāng)你每個(gè)步驟都能做到很優(yōu)秀的時(shí)候,你應(yīng)該考慮如何組合這四個(gè)步驟,使你的爬蟲達(dá)到效率最高,也就是所謂的爬蟲策略問題,爬蟲策略學(xué)習(xí)不是一朝一夕的事情,建議多看看一些比較優(yōu)秀的爬蟲的設(shè)計(jì)方案,比如說。 (一)如何學(xué)習(xí)Python 學(xué)習(xí)Python大致可以分為以下幾個(gè)階段: 1.剛上手的時(shí)候肯定是先過一遍Python最基本的知識,比如說:變量、數(shù)據(jù)結(jié)構(gòu)、語法...
閱讀 1413·2023-04-26 03:04
閱讀 2365·2019-08-30 15:44
閱讀 3736·2019-08-30 14:15
閱讀 3541·2019-08-27 10:56
閱讀 2758·2019-08-26 13:53
閱讀 2626·2019-08-26 13:26
閱讀 3088·2019-08-26 12:11
閱讀 3618·2019-08-23 18:21