Python的asyncio是使用 async/await 文法編寫并發代碼的标準庫。通過上一篇文章,我們了解了它不斷變化的發展曆史。到了Python最新穩定版 3.7 這個版本,asyncio又做了比較大的調整,把這個庫的API分為了 高層級API和低層級API,并引入asyncio.run()這樣的進階方法,讓編寫異步程式更加簡潔。

本文希望提綱挈領地介紹最新 3.7 版的asnycio,先從全局認識Python這個異步IO庫。
asyncio的高層級API主要提高如下幾個方面:
– 并發地運作Python協程并完全控制其執行過程;
– 執行網絡IO和IPC;
– 控制子程序;
– 通過隊列實作分布式任務;
– 同步并發代碼。
asyncio的低層級API用以支援開發異步庫和架構:
– 建立和管理事件循環(event loop),提供異步的API用于網絡,運作子程序,處理作業系統信号等;
– 通過transports實作高效率協定;
– 通過async/await 文法橋架基于回調的庫和代碼。
asyncio進階API
高層級API讓我們更友善的編寫基于asyncio的應用程式。這些API包括:
(1)協程和任務
協程通過 async/await 文法進行聲明,是編寫異步應用的推薦方式。曆史的 @asyncio.coroutine 和 yield from 已經被棄用,并計劃在Python 3.10中移除。協程可以通過 asyncio.run(coro, *, debug=False) 函數運作,該函數負責管理事件循環并完結異步生成器。它應該被用作asyncio程式的主入口點,相當于main函數,應該隻被調用一次。
任務被用于并發排程協程,可用于網絡爬蟲的并發。使用 asyncio.create_task() 就可以把一個協程打包為一個任務,該協程會自動安排為很快運作。
協程,任務和Future都是可等待對象。其中,Future是低層級的可等待對象,表示一個異步操作的最終結果。
(2)流
流是用于網絡連接配接的高層級的使用 async/await的原語。流允許在不使用回調或低層級協定和傳輸的情況下發送和接收資料。異步讀寫TCP有用戶端函數 asyncio.open_connection() 和 服務端函數 asyncio.start_server() 。它還支援 Unix Sockets: asyncio.open_unix_connection() 和 asyncio.start_unix_server()。
(3)同步原語
asyncio同步原語的設計類似于threading子產品的原語,有兩個重要的注意事項:
asyncio原語不是線程安全的,是以它們不應該用于OS線程同步(而是用threading)
這些同步原語的方法不接受逾時參數; 使用asyncio.wait_for()函數執行逾時操作。
asyncio具有以下基本同步原語:
Lock
Event
Condition
Semaphore
BoundedSemaphore
(4)子程序
asyncio提供了通過 async/await 建立和管理子程序的API。不同于Python标準庫的subprocess,asyncio的子程序函數都是異步的,并且提供了多種工具來處理這些函數,這就很容易并行執行和監視多個子程序。建立子程序的方法主要有兩個:
coroutine asyncio.create_subprocess_exec()
coroutine asyncio.create_subprocess_shell()
(5)隊列
asyncio 隊列的設計類似于标準子產品queue的類。雖然asyncio隊列不是線程安全的,但它們被設計為專門用于 async/await 代碼。需要注意的是,asyncio隊列的方法沒有逾時參數,使用 asyncio.wait_for()函數進行逾時的隊列操作。
因為和标注子產品queue的類設計相似,使用起來跟queue無太多差異,隻需要在對應的函數前面加 await 即可。asyncio 隊列提供了三種不同的隊列:
class asyncio.Queue 先進先出隊列
class asyncio.PriorityQueue 優先隊列
class asyncio.LifoQueue 後進先出隊列
(6)異常
asyncio提供了幾種異常,它們是:
TimeoutError,
CancelledError,
InvalidStateError,
SendfileNotAvailableError
IncompleteReadError
LimitOverrunError
asyncio低級API
低層級API為編寫基于asyncio的庫和架構提供支援,有意編寫異步庫和架構的大牛們需要熟悉這些低層級API。主要包括:
(1)事件循環
事件循環是每個asyncio應用程式的核心。 事件循環運作異步任務和回調,執行網絡IO操作以及運作子程序。
應用程式開發人員通常應該使用進階asyncio函數,例如asyncio.run(),并且很少需要引用循環對象或調用其方法。
Python 3.7 新增了 asyncio.get_running_loop()函數。
(2)Futures
Future對象用于将基于低層級回調的代碼與高層級的 async/await 代碼進行橋接。
Future表示異步操作的最終結果。 不是線程安全的。
Future是一個可等待對象。 協程可以等待Future對象,直到它們有結果或異常集,或者直到它們被取消。
通常,Futures用于啟用基于低層級回調的代碼(例如,在使用asyncio傳輸實作的協定中)以與高層級 async/await 代碼進行互操作。
(3)傳輸和協定(Transports和Protocols)
Transport 和 Protocol由低層級事件循環使用,比如函數loop.create_connection()。它們使用基于回調的程式設計風格,并支援網絡或IPC協定(如HTTP)的高性能實作。
在最進階别,傳輸涉及位元組的傳輸方式,而協定确定要傳輸哪些位元組(在某種程度上何時傳輸)。
換種方式說就是:傳輸是套接字(或類似的I/O端點)的抽象,而協定是從傳輸的角度來看的應用程式的抽象。
另一種觀點是傳輸和協定接口共同定義了一個使用網絡I/O和程序間I/O的抽象接口。
傳輸和協定對象之間始終存在1:1的關系:協定調用傳輸方法來發送資料,而傳輸調用協定方法來傳遞已接收的資料。
大多數面向連接配接的事件循環方法(例如loop.create_connection())通常接受protocol_factory參數,該參數用于為接受的連接配接建立Protocol對象,由Transport對象表示。 這些方法通常傳回(傳輸,協定)元組。
(4)政策(Policy)
事件循環政策是一個全局的按程序劃分的對象,用于控制事件循環的管理。 每個事件循環都有一個預設政策,可以使用政策API對其進行更改和自定義。
政策定義了上下文的概念,并根據上下文管理單獨的事件循環。 預設政策将上下文定義為目前線程。
通過使用自定義事件循環政策,可以自定義get_event_loop(),set_event_loop()和new_event_loop()函數的行為。
(5)平台支援
asyncio子產品設計為可移植的,但由于平台的底層架構和功能,某些平台存在細微的差異和限制。在Windows平台,有些是不支援的,比如 loop.create_unix_connection() and loop.create_unix_server()。而Linux和比較新的macOS全部支援。
結語
Python 3.7 通過對asyncio分組使得它的架構更加清晰,普通寫異步IO的應用程式隻需熟悉高層級API,需要寫異步IO的庫和架構時才需要了解低層級的API。
後面,我們将結合具體執行個體來學習asyncio的使用。如果你有好的建議和意見,歡迎留言與我們讨論。
我的公衆号:猿人學 Python 上會分享更多心得體會,敬請關注。
***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***