I/O事件
最近在研究tornado和gevent,里面涉及了非阻塞I/O。在了解非阻塞I/O之前,需要先了解I/O事件
我們知道,內核有緩沖區。假設有兩個進程A,B,進程B想讀進程A寫入的東西(即進程A做寫操作,B做讀操作)。進程A需要先寫入到內核緩沖區中,然后B從內核緩沖區中讀取,如圖:

進程B會監聽內核緩沖區的變化
I/O事件的阻塞與同步
- 當內核緩沖區為空的時候,進程B會阻塞住
- 當A往內核緩沖區寫入時,內核緩沖區就不是空狀態了,這時候就會喚醒進程B
- 如果緩沖區滿了,但是進程B沒有被喚醒,就會通知進程A,告訴A不要再寫入數據了,也就是進程A被阻塞
- 當進程B被喚醒后,B就從緩沖區讀取數據,由於B在讀數據,緩沖區就不會是滿的狀態了,這時候就會通知A繼續寫數據,也就是進程A被喚醒
- 如果進程A還沒有喚醒,而緩沖區被B讀完了(緩沖區為空),這時候就會阻塞進程B
阻塞I/O的缺點
在阻塞I/O情況下,一個線程只能處理一個流的I/O事件。也就是說,如果想處理多個流的I/O事件,就必須使用多進程(fork),或者多線程——效率太低
處理I/O的第二種方法
除了使用阻塞I/O,還可以使用非阻塞I/O的方式。
最開始能想到的就是用輪詢的方法:依次詢問每個流,如果緩沖區不為空,就進行操作;否則,詢問下一個流
但是這種方法效率很低,會白白浪費掉CPU資源。於是便引入了代理——poll
poll
poll代理可以同時觀察很多I/O流事件,在空閑的時候(即沒有I/O事件的時候),會阻塞當前線程;當有I/O事件的時候,會被喚醒,然后把所有流輪詢一遍
這樣就能通過減少盲目的輪詢來減少對CPU資源的浪費
但是,使用這個也有缺點:由於每次喚醒都需要把所有流都輪詢一遍,當流很多的時候,輪詢的時間會很長
poll進化版——epoll
epoll是基於事件的輪詢,它會記錄是哪個流產生了I/O事件,然后針對這個流來進行操作,大大降低了復雜度
