Python异步编程原理篇之协程的IO
協程的IO
asyncio 作為實現異步編程的庫,任務執行中遇到系統IO的時能夠自動切換到其他任務。協程使用的IO模型是IO多路復用。在 asyncio 低階API 一篇中提到過 “以Linux系統為例,IO模型有阻塞,非阻塞,IO多路復用等。asyncio 常用的是IO多路復用模型的epool和 kqueue”。本篇就介紹一下IO多路復用技術以及操作系統的IO,為后續內容做一個鋪墊。
什么是IO
根據馮.諾依曼結構,它將計算機分成分為5個部分:運算器、控制器、存儲器、輸入設備、輸出設備。
涉及計算機核心與其他設備間數據遷移的過程就是IO
常見的IO包括:文件的讀寫、網絡請求。
以文件讀寫為例,一個應用程序讀一個文件。一個應用程序就是一個進程,操作系統為每一個進程分配的內存分為兩個部分,分別是用戶空間和內核空間。以32位系統為例,用戶空間分配3GB,內核空間分為1GB。IO操作因為都是和硬件設備交互,所以不能讓用戶進程直接操作,而是需要進程調用操作提供提供的API來完成。
應用程序讀一個文件的流程是:
- 應用程序調用系統提供讀文件的命令
- 系統將磁盤中文件內容讀取到內核空間
- 系統將文件內容從內核空間拷貝的用戶空間
- 應用程序讀取用戶空間中的文件內容
從文件IO總結IO的基本流程為:
總結來看,IO操作的基本流程是:
- 應用程序發起IO調用
- 操作系統完成IO操作
阻塞IO模型
阻塞IO模型就是應用程序發起IO調用之后一直阻塞等待,一直等到數據從內核空間拷貝用戶空間,此次調用才算完成。
流程圖如下:
存在問題:
如果內核數據一直沒準備好,那用戶進程將一直阻塞,CPU空轉而浪費時間。并發大的情況下將導致進程數量變大,限制并發數量。
非阻塞IO模型
應用程序發起IO調用,如果內核空間數據還沒讀取完成,可以先返回錯誤信息給用戶進程,讓它不需要等待,而是通過輪詢的方式再來請求。這就是非阻塞IO,流程圖如下:
非阻塞IO的流程如下:
- 應用進程向操作系統內核,發起讀取數據。
- 操作系統內核數據沒有準備好,立即返回錯誤碼。
- 應用程序輪詢調用,繼續向操作系統內核發起讀取數據。
- 操作系統內核空間讀取數據完成,從內核緩沖區拷貝到用戶空間。
- 完成調用,返回成功提示。
存在問題:
它相對于阻塞IO,雖然大幅提升了性能,但是它依然存在性能問題,即頻繁的輪詢,導致頻繁的系統調用,同樣會消耗大量的CPU資源。
IO多路復用模型
非阻塞IO的問題
非阻塞IO模型下并發情況下應用程序可能會發送上千次請求,如果每一次請求的IO都需要輪詢獲取結果,那么應用就需要創建上千個線程去輪詢監聽數據是否拷貝完成。
這么多的線程不斷調用系統函數 recvfrom 請求數據,首先服務器不能支持這么多請求,其次這種方式太浪費資源了,線程是我們操作系統的寶貴資源,大量的線程用來去讀取數據了,那么就意味著能做其它事情的線程就會少。如何解決這個問題呢?使用IO多路復用可以將輪詢監聽的線程降低到1個。
IO多路復用介紹
IO多路復用的原理:
可以由一個線程監控多個網絡請求,當有數據準備好之后再通知對應的線程去讀取數據。這樣就可以只需要一個線程完成數據是否就緒狀態的查詢。通過復用一個輪詢的線程節省出大量的線程資源出來,這個就是IO復用模型的思路。
IO多路復用的流程:
- 應用程序調用IO請求返回一個文件描述符
- IO多路復用的函數(select、poll、epoll)同時監控多個文件描述符
- 當某一個文件描述符的狀態變成就緒時,IO多路復用函數通知對應應用程序
- 應用程序讀取文件,數據從內核空間拷貝的用戶空間,完成數據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
總結
以上是生活随笔為你收集整理的Python异步编程原理篇之协程的IO的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Contest3376 - 2024寒假
- 下一篇: 使用 cProfile 和火焰图调优 P