1. 程式人生 > >看看八年前的(阻塞、非阻塞、多路複用、訊號驅動、非同步)IO總結

看看八年前的(阻塞、非阻塞、多路複用、訊號驅動、非同步)IO總結

尊重原創,來自八年前的IO總結https://blog.csdn.net/historyasamirror/article/details/4270633

阻塞IO:

  • 執行過程

使用者請求資料,系統核心(kernel)開始準備資料,使用者程序被阻塞,資料被準備好後,kernel會將資料拷貝到使用者記憶體,拷貝的過程中使用者程序也被阻塞,直到kernel返回結果後,使用者程序才解除阻塞,重新執行起來。

  • 解決辦法:

在伺服器端使用多程序或多執行緒,讓每個連線都擁有獨立的執行緒或程序。

  • 暴露的問題:

連線請求多的時候,佔用系統資源,降低系統對外界響應速度。

  • 改進方案:

執行緒池:減少建立和銷燬執行緒的頻率
連線池:維持連線的快取池,儘量重用已有的連線、減少建立和關閉連線的頻率

  • 暴漏的問題:

池有上限,當請求超過上限,同樣會出現問題。所以使用池,必須考慮其面臨的響應規模和池的大小。

非阻塞IO:

  • 執行過程

當用戶程序發出read操作時,程序並沒有被阻塞,核心馬上返回給程序響應,如果資料還沒準備好,此時會返回一個error。程序在返回之後,可以乾點別的事情,然後再發起recvform系統呼叫。重複上面的過程,迴圈往復的進行recvform系統呼叫。這個過程通常被稱之為輪詢。輪詢檢查核心資料,直到資料準備好,再拷貝資料到程序,進行資料處理。需要注意,拷貝資料整個過程,程序仍然是屬於阻塞的狀態。

多路複用IO:

  • 執行過程

它的基本原理就是select/epoll這個函式會不斷的輪詢所負責的所有socket,當某個socket有資料到達了,就通知使用者程序 ,select/epoll的好處就在於單個執行緒/程序,就可以同時處理多個網路連線的IO。如圖:
在這裡插入圖片描述
當用戶程序呼叫了select,那麼整個程序會被阻塞,而同時,系統核心會“監視”所有select負責的socket,當任何一個socket中的資料準備好了,select就會返回。這個時候使用者程序再呼叫read操作,將資料從kernel系統核心拷貝到使用者程序。

  • 與阻塞io的區別:

1、多路複用需要使用兩個系統呼叫:select和recvfrom,而阻塞IO只調用了一個系統呼叫(recvfrom)。但是,用select的優勢在於它可以同時處理多個connection。
2、如果處理的連線數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server效能更好,可能延遲還更大。select/epoll的優勢並不是對於單個連線能處理得更快,而是在於能處理更多的連線。
3、在多路複用模型中,對於每一個socket,一般都設定成為non-blocking,但是,整個使用者的process其實是一直被阻塞的。只不過process是被select這個函式阻塞的,而不是被socket IO給阻塞。

  • 暴漏的問題:

1、select()介面並不是實現“事件驅動”的最好選擇。因為當需要探測的控制代碼值較大時,select()介面本身需要消耗大量時間去輪詢各個控制代碼(select在後續部落格寫)。
2、該模型將事件探測和事件響應夾雜在一起,一旦事件響應的執行體龐大,則對整個模型是災難性的。

訊號驅動IO(signal driven IO)

  • 原理

首先我們允許套介面進行訊號驅動I/O,並安裝一個訊號處理函式,程序繼續執行並不阻塞。當資料準備好時,程序會收到一個SIGIO訊號,可以在訊號處理函式中呼叫I/O操作函式處理資料。當資料報準備好讀取時,核心就為該程序產生一個SIGIO訊號。我們隨後既可以在訊號處理函式中呼叫recvfrom讀取資料報,並通知主迴圈資料已準備好待處理,也可以立即通知主迴圈,讓它來讀取資料報。無論如何處理SIGIO訊號,這種模型的優勢在於等待資料報到達(第一階段)期間,程序可以繼續執行,不被阻塞。免去了select的阻塞與輪詢,當有活躍套接字時,由註冊的handler處理。

非同步IO:

  • 執行過程

非同步IO是真正非阻塞的,它不會對請求程序產生任何的阻塞,因此對高併發的網路伺服器實現至關重要。
使用者程序發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從系統核心的角度,當它收到一個asynchronous(非同步) read之後,首先它會立刻返回,所以不會對使用者程序產生任何阻塞。然後,系統核心會等待資料準備完成,然後將資料拷貝到使用者記憶體,當這一切都完成之後,kernel系統核心會給使用者程序傳送一個signal,告訴它read操作完成了。

synchronous(同步) IO和asynchronous(非同步) IO的區別:

兩者的區別就在於synchronous IO做”IO operation”的時候會將process阻塞。按照這個定義,之前所述的blocking IO,non-blocking IO,IO multiplexing(複用)都屬於synchronous IO。有人可能會說,non-blocking IO並沒有被block啊。這裡有個非常“狡猾”的地方,定義中所指的”IO operation”是指真實的IO操作。non-blocking IO在執行recvfrom這個系統呼叫的時候,如果kernel的資料沒有準備好,這時候不會block程序。但是當kernel中資料準備好的時候,recvfrom會將資料從kernel拷貝到使用者記憶體中,這個時候程序是被block了,在這段時間內程序是被block的。而asynchronous IO則不一樣,當程序發起IO操作之後,就直接返回再也不理睬了,直到kernel傳送一個訊號,告訴程序說IO完成。在這整個過程中,程序完全沒有被block。
asynchronous IO,它就像是使用者程序將整個IO操作交給了他人(kernel)完成,然後他人做完後發訊號通知。在此期間,使用者程序不需要去檢查IO操作的狀態,也不需要主動的去拷貝資料。

  • 舉例:

A:阻塞IO,B:非阻塞IO,C:多路複用IO,D:非同步IO,E:訊號驅動IO,五個人在釣魚:
A用的是最老式的魚竿,所以呢,得一直守著,等到魚上鉤了再拉桿;
B的魚竿有個功能,能夠顯示是否有魚上鉤,所以呢,B就和旁邊的MM聊天,隔會再看看有沒有魚上鉤,有的話就迅速拉桿;
C用的魚竿和B差不多,魚竿有顯示是否上鉤的功能,但他想了一個好辦法,就是同時放好幾根魚竿(select/poll/epoll),然後守在旁邊,一旦有顯示說魚上鉤了,它就將對應的魚竿拉起來;
D是個有錢人,乾脆僱了一個人(kernel)幫他釣魚,一旦那個人把魚釣上來了,就給D發個簡訊,並送魚上門;
E的魚竿比較高階,魚竿自帶一個訊號器,E不需要守著魚竿,魚上鉤後,訊號器自動給E傳送簡訊通知,E過來取魚。