Python異步編程原理篇之協程的IO

image

協程的IO

asyncio 作爲實現異步編程的庫,任務執行中遇到系統IO的時能夠自動切換到其他任務。協程使用的IO模型是IO多路複用。asyncio 低階API 一篇中提到過 “以Linux系統爲例,IO模型有阻塞,非阻塞,IO多路複用等。asyncio 常用的是IO多路複用模型的epoolkqueue”。本篇就介紹一下IO多路複用技術以及操作系統的IO,爲後續內容做一個鋪墊。

什麼是IO

根據馮.諾依曼結構,它將計算機分成分爲5個部分:運算器、控制器、存儲器、輸入設備、輸出設備。
涉及計算機核心與其他設備間數據遷移的過程就是IO

常見的IO包括:文件的讀寫、網絡請求。
以文件讀寫爲例,一個應用程序讀一個文件。一個應用程序就是一個進程,操作系統爲每一個進程分配的內存分爲兩個部分,分別是用戶空間和內核空間。以32位系統爲例,用戶空間分配3GB,內核空間分爲1GB。IO操作因爲都是和硬件設備交互,所以不能讓用戶進程直接操作,而是需要進程調用操作提供提供的API來完成。

應用程序讀一個文件的流程是:

  1. 應用程序調用系統提供讀文件的命令
  2. 系統將磁盤中文件內容讀取到內核空間
  3. 系統將文件內容從內核空間拷貝的用戶空間
  4. 應用程序讀取用戶空間中的文件內容

從文件IO總結IO的基本流程爲:

總結來看,IO操作的基本流程是:

  1. 應用程序發起IO調用
  2. 操作系統完成IO操作

阻塞IO模型

阻塞IO模型就是應用程序發起IO調用之後一直阻塞等待,一直等到數據從內核空間拷貝用戶空間,此次調用纔算完成。
流程圖如下:

存在問題:
如果內核數據一直沒準備好,那用戶進程將一直阻塞,CPU空轉而浪費時間。併發大的情況下將導致進程數量變大,限制併發數量。

非阻塞IO模型

應用程序發起IO調用,如果內核空間數據還沒讀取完成,可以先返回錯誤信息給用戶進程,讓它不需要等待,而是通過輪詢的方式再來請求。這就是非阻塞IO,流程圖如下:

非阻塞IO的流程如下:

  • 應用進程向操作系統內核,發起讀取數據。
  • 操作系統內核數據沒有準備好,立即返回錯誤碼。
  • 應用程序輪詢調用,繼續向操作系統內核發起讀取數據。
  • 操作系統內核空間讀取數據完成,從內核緩衝區拷貝到用戶空間。
  • 完成調用,返回成功提示。

存在問題:
它相對於阻塞IO,雖然大幅提升了性能,但是它依然存在性能問題,即頻繁的輪詢,導致頻繁的系統調用,同樣會消耗大量的CPU資源。

IO多路複用模型

非阻塞IO的問題

非阻塞IO模型下併發情況下應用程序可能會發送上千次請求,如果每一次請求的IO都需要輪詢獲取結果,那麼應用就需要創建上千個線程去輪詢監聽數據是否拷貝完成。

這麼多的線程不斷調用系統函數 recvfrom 請求數據,首先服務器不能支持這麼多請求,其次這種方式太浪費資源了,線程是我們操作系統的寶貴資源,大量的線程用來去讀取數據了,那麼就意味着能做其它事情的線程就會少。如何解決這個問題呢?使用IO多路複用可以將輪詢監聽的線程降低到1個。

IO多路複用介紹

IO多路複用的原理
可以由一個線程監控多個網絡請求,當有數據準備好之後再通知對應的線程去讀取數據。這樣就可以只需要一個線程完成數據是否就緒狀態的查詢。通過複用一個輪詢的線程節省出大量的線程資源出來,這個就是IO複用模型的思路。

IO多路複用的流程

  1. 應用程序調用IO請求返回一個文件描述符
  2. IO多路複用的函數(select、poll、epoll)同時監控多個文件描述符
  3. 當某一個文件描述符的狀態變成就緒時,IO多路複用函數通知對應應用程序
  4. 應用程序讀取文件,數據從內核空間拷貝的用戶空間,完成數據IO

IO多路複用使用的函數有三種,分別是:select、poll、epoll。三者在實現上有一些區別。IO多路複用實現的核心思想是監聽文件描述符fd的狀態,當fd狀態就緒時通知對應的應用讀取數據。

select

應用進程通過調用select函數,可以同時監控多個文件描述符。在select函數監控的fd中,只要有任何一個數據狀態準備就緒了,select函數就會返回可讀狀態,這時應用進程再發起recvfrom請求去讀取數據。

select缺點:

  • 監聽的IO最大連接數有限,在Linux系統上一般爲1024。
  • select函數是通過遍歷fdset,找到就緒的描述符fd。遍歷的時間性能消耗較大

poll

由於select存在連接數限制,所以後來又提出了poll。poll模型裏面通過使用鏈表的形式來保存自己監控的fd信息,連接數限制問題。

缺點:
select和poll一樣,還是需要通過遍歷文件描述符來獲取已經就緒的socket。如果同時連接的大量客戶端在一時刻可能只有極少處於就緒狀態,伴隨着監視的描述符數量的增長,效率也會線性下降。

epoll

epoll並不是像select一樣去遍歷事件列表逐個輪詢的監控fd的事件狀態,而是事先就建立了fd與之對應的回調函數,當事件激活後主動回調將fd加入到就緒鏈表中,這也就避免了遍歷事件列表的這個操作。
這裏去掉了遍歷文件描述符的低性能操作,而是採用監聽事件回調的的機制。這就是epoll的亮點。

小結

需要注意的是IO多路複用也是阻塞的IO,只不過它能併發處理的IO效率更高。

信號驅動模型

信號驅動IO不再用主動詢問的方式去確認數據是否就緒,而是向內核發送一個信號(調用sigaction的時候建立一個SIGIO的信號),然後應用用戶進程可以去做別的事,不用阻塞。當內核數據準備好後,再通過SIGIO信號通知應用進程,數據準備好後的可讀狀態。應用用戶進程收到信號之後,立即調用recvfrom,去讀取數據。

信號驅動IO模型,在應用進程發出信號後,是立即返回的,不會阻塞進程。它已經有異步操作的感覺了。但是數據複製到應用緩衝的時候,應用進程還是阻塞的。
回過頭來看下,不管是非阻塞IO、IO多路複用還是信號驅動,在數據從內核複製到應用緩衝的時候,都是阻塞的。

異步IO模型

非阻塞IO、IO多路複用還是信號驅動在數據從內核複製到應用緩衝的時候,都是阻塞的,因此都不是真正的異步。
異步IO實現了IO全流程的非阻塞,就是應用進程發出系統調用後,是立即返回的,但是立即返回的不是處理結果,而是表示提交成功類似的意思。等內核數據準備好,將數據拷貝到用戶進程緩衝區,發送信號通知用戶進程IO操作執行完畢。

異步IO的原理很簡單,只需要向內核發送一次請求,就可以完成數據狀態詢問和數據拷貝的所有操作,並且不用阻塞等待結果。

同步、異步、阻塞、非阻塞總結

相關術語:

  • 同步阻塞(blocking-IO)簡稱BIO
  • 同步非阻塞(non-blocking-IO)簡稱NIO
  • 異步非阻塞(asynchronous-non-blocking-IO)簡稱AIO

參考文章:
https://zhuanlan.zhihu.com/p/439770090

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章