python基礎教程:非同步IO 之 概念和歷史
程式設計中,我們經常會遇到“併發”這個概念,目的是讓軟體能充分利用硬體資源,提高效能。併發的方式有多種,多執行緒,多程序,非同步IO等。多執行緒和多程序更多應用於CPU密集型的場景,比如科學計算的時間都耗費在CPU上,利用多核CPU來分擔計算任務。多執行緒和多程序之間的場景切換和通訊代價很高,不適合IO密集型的場景(關於多執行緒和多程序的特點已經超出本文討論的範疇,有興趣的同學可以自行搜尋深入理解)。而非同步IO就是非常適合IO密集型的場景,比如網路爬蟲和Web服務。

在計算機程式中,IO就是讀寫磁碟、讀寫網路的操作,這種讀寫速度比讀寫記憶體、CPU快取慢得多,前者的耗時是後者的成千上萬倍甚至更多。這就導致,IO密集型的場景99%以上的時間都花費在IO等待的時間上。非同步IO就是把CPU從漫長的等待中解放出來的方法。這就可以大大提高我們寫的軟體系統的併發性。這樣的軟體,可以是網路爬蟲,也可以是Web服務等一切IO密集型的系統。
非同步IO的優勢顯而易見,各種語言都通過實現這個機制來提高自身的效率,Python也不例外。Python經歷了2和3兩個大版本的躍遷。這其中也有對非同步IO支援的變化歷程。
Python 2的非同步IO庫
Python 2 時代官方並沒有非同步IO的支援,但是有幾個第三方庫通過事件或事件迴圈(Event Loop)實現了非同步IO,它們是:
- twisted: 是事件驅動的網路庫
- gevent: greenlet + libevent(後來是libev或libuv)。通過協程(greenlet)和事件迴圈庫(libev,libuv)實現的gevent使用很廣泛。
- tornado: 支援非同步IO的web框架。自己實現了IOLOOP。
Python 3 官方的非同步IO
Python 3.4 加入了asyncio 庫,使得Python有了支援非同步IO的官方庫。這個庫,底層是事件迴圈(EventLoop),上層是協程和任務。asyncio自從3.4 版本加入到最新的 3.7版一直在改進中。
Python 3.4 剛開始的asyncio的協程還是基於生成器的,通過 yield from 語法實現,可以通過裝飾器 @asyncio.coroutine (已過時)裝飾一個函式來定義一個協程。比如:

Python 3.5 引入了兩個新的關鍵字 await 和 async 用來替換 @asyncio.coroutine 和 yield from ,從語言本身來支援非同步IO。從而使得非同步程式設計更加簡潔,並和普通的生成器區別開來。
注意: 對基於生成器的協程的支援已棄用,並計劃在 Python 3.10 中移除。所以,寫非同步IO程式時只需使用 async 和 await 即可。
Python 3.7 又進行了優化,把API分組為高層級API和低層級API。 我們先看看下面的程式碼,發現與上面的有什麼不同?

除了用 async 替換 @asyncio.coroutine 和用 await 替換 yield from 外,最大的變化就是關於eventloop的程式碼不見了,只有一個 async.run()。這就是 3.7 的改進,把eventloop相關的API歸入到低層級API,新引進run()作為高層級API讓寫應用程式的開發者呼叫,而不用再關心eventloop。除非你要寫非同步庫(比如MySQL非同步庫)才會和eventloop打交道。
需要注意的是, async.run() 是3.7版新增加的,處於暫定API狀態。 暫定API,是指被有意排除在標準庫的向後相容性保證之外的應用程式設計介面。雖然此類介面通常不會再有重大改變,但只要其被標記為暫定,就可能在核心開發者確定有必要的情況下進行向後不相容的更改(甚至包括移除該介面)。此種更改並不會隨意進行 — 僅在 API 被加入之前未考慮到的嚴重基礎性缺陷被發現時才可能會這樣做。即便是對暫定 API 來說,向後不相容的更改也會被視為“最後的解決方案” —— 任何問題被確認時都會盡可能先嚐試找到一種向後相容的解決方案。這種處理過程允許標準庫持續不斷地演進,不至於被有問題的長期性設計缺陷所困。
從上面關於 asyncio 的發展來看它一直在變化,3.4,3.5,3.6, 3.7 都有很多細節上的變化。當我看到3.7的run()函式時,也發現一年前基於3.6的asnycio寫的爬蟲不那麼優雅了。
這種變化,一方面改善了asyncio本身的效能和使用方便程度,但另一方面也增加了我們使用者的學習成本、Python升級帶來的改造的成本。如果你以消極的態度抵制這種變化,可以去學習golang,C++來實現你的程式;如果你以積極的態度迎接這種變化,可以更快的掌握這種變化,並優雅 高效的實現你的程式。
只要你喜歡用Python寫程式解決問題,那麼就接受並掌握這種變化吧。其實,那種語言不在變,那種技術不在前進。作為程式設計師,你只有不斷地學習和前進。
uvloop
uvloop是用Cython寫的,基於libuv這個C語言實現的高效能非同步I/O庫。asyncio自己的事件迴圈是用Python寫的,用uvloop替換asyncio自己的事件迴圈可以是asyncio的速度更快。並且使用相當簡潔:

大家在學python的時候肯定會遇到很多難題,以及對於新技術的追求,這裡推薦一下我們的Python學習扣qun:784758214,這裡是python學習者聚集地!!同時,自己是一名高階python開發工程師,從基礎的python指令碼到web開發、爬蟲、django、資料探勘等,零基礎到專案實戰的資料都有整理。送給每一位python的小夥伴!每日分享一些學習的方法和需要注意的小細節
