I/O多路復用模型

背景

在文章中提到了五種I/O模型,其中前四種:阻塞模型、非阻塞模型、信號驅動模型、I/O復用模型都是同步模型;還有一種是異步模型。

想寫一個系列的文章,介紹從I/O多路復用到異步編程和RPC框架,整個演進過程,這一系列可能包括:

  1. Reactor和Proactor模型
  2. 為什麼需要異步編程
  3. enable_shared_from_this用法分析
  4. 網絡通信庫和RPC

為什麼有多路復用?

多路復用技術要解決的是“通信”問題,解決核心在於“同步事件分離器”(de-multiplexer),linux系統帶有的分離器select、poll、epoll網上介紹的比較多,大家可以看看這篇介紹的不錯的文章:。通信的一方想要知道另一方的狀態(以決定自己做什麼),有兩種方法: 一是輪詢,二是消息通知。

輪詢

輪詢的一種典型的實現可能是這樣的:當然這裏的epoll_wait()也可以使用poll()或者select()替換。

whiletrue) {
    active_stream[] = epoll_wait(epollfd)
    for i in active_stream[] {
        read or write till
    }
}

輪詢方式主要存在以下不足:

  • 增加系統開銷。無論是任務輪詢還是定時器輪詢都需要消耗對應的系統資源。
  • 無法及時感知設備狀態變化。在輪詢間隔內的設備狀態變化只有在下次輪詢時才能被發現,這將無法滿足對實時性敏感的應用場合。
  • 浪費CPU資源。無論設備是否發生狀態改變,輪詢總在進行。在實際情況中,大多數設備的狀態改變通常不會那麼頻繁,輪詢空轉將白白浪費CPU時間片。

消息通知

其實現方式通常是: “阻塞-通知”機制。阻塞會導致一個任務(task_struct,進程或者線程)只能處理一個”I/O流”或者類似的操作,要處理多個,就要多個任務(需要多個進程或線程),因此靈活性上又不如輪詢(一個任務足夠),很矛盾。

 

select、poll、epoll對比

矛盾的根源就是”一”和”多”的矛盾: 希望一個任務處理多個對象,同時避免處理阻塞-通知機制的內部細節。解決方案是多路復用(muliplex)。多路復用有3種基本方案,select()/poll()/epoll(),都是來解決這一矛盾的。

  • 通知代理: 用戶把需要關心的對象註冊給select()/poll()/epoll()函數。
  • 一對多: 所有的被關心的對象,只要有一個對象有了通知事件,select()/poll()/epoll()就會結束阻塞狀態。
  • 方便性: 用戶(程序員)不用再關心如何阻塞和被通知,以及哪些情況下會有通知產生。這件事情已經由上述幾個系統調用做了,用戶只需要實現”通知來了我該做什麼”。

 

那麼上面3個系統調用的區別是什麼呢?
第一個select(),結合了輪詢和阻塞兩種方式,沒有問題,每次有一個對象事件發生的時候,select()只是知道有事件發生了,具體是哪個對象發生的,不知道,需要從頭到尾輪詢一遍,複雜度是O(n)。poll函數相對select函數變化不大,只是提升了最大的可輪詢的對象個數。epoll函數把時間複雜度降到O(1)。

 

為什麼select慢而epoll效率高?
select()之所以慢,有幾個原因: select()的參數是一個FD數組,意味着每次select調用,都是一次新的註冊-阻塞-回調,每次select都要把一個數組從用戶空間拷貝到內核空間,內核檢測到某個對象狀態變化並寫入后,再從內核空間拷貝回用戶空間,select再把這個數組讀取一遍,並返回。這個過程非常低效。

epoll的解決方案相當於是一種對select()的算法優化: 它把select()一個函數做的事情分解成了3步,首先epoll_create()創建一個epollfd對象(相當於一個池子),然後所有被監聽的fd通過epoll_ctrl()註冊到這個池子,也就是為每個fd指定了一個內部的回調函數(這樣,就沒有了每次調用時的來回拷貝,用戶空間的數組到內核空間只有這一次拷貝)。epoll_wait阻塞等待。在內核態有一個和epoll_wait對應的函數調用,把就緒的fd,填入到一個就緒列表中,而epoll_wait讀取這個就緒列表,做到了快速返回(O(1))。

詳細的對比可以參考select、poll、epoll之間的區別總結:

 

有了上面的原理介紹,這裏舉例來說明下epoll到底是怎麼使用的,加深理解。舉兩個例子:

一個是比較簡單的父子進程通信的例子,單個小程序,不需要跑多個應用實例,不需要用戶輸入。
一個是比較實戰的socket+epoll,畢竟現實案例中哪有兩個父子進程間通訊這麼簡單的應用場景。

有了多路復用,難道還不夠?

有了I/O復用,有了epoll已經可以使服務器併發幾十萬連接的同時,維持高TPS了,難道這還不夠嗎?答案是,技術層面足夠了,但在軟件工程層面卻是不夠的。例如,總要有個for循環去調用epoll,總來處理epoll的返回,這是每次都要重複的工作。for循環體裏面寫什麼—-通知返回之後,做事情的程序最好能以一種回調的機制,提供一個編程框架,讓程序更有結構一些。另一方面,如果希望每個事件通知之後,做的事情能有機會被代理到某個線程裏面去單獨運行,而線程完成的狀態又能通知回主任務,那麼”異步”的進制就必須被引入。

所以,還有兩個問題要解決,一是”編程框架”,一是”異步”。我們先看幾個目前流行的框架,大部分框架已經包含了某種異步的機制。我們接下來的篇章將介紹“編程框架”和“異步I/O模型”。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

您可能也會喜歡…