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