背景
在文章《unix網絡編程》(12)五種I/O模型中提到了五種I/O模型,其中前四種:阻塞模型、非阻塞模型、信號驅動模型、I/O複用模型都是同步模型;還有一種是異步模型。html
想寫一個系列的文章,介紹從I/O多路複用到異步編程和RPC框架,整個演進過程,這一系列可能包括:linux
爲何有多路複用?
多路複用技術要解決的是「通訊」問題,解決核心在於「同步事件分離器」(de-multiplexer),linux系統帶有的分離器select、poll、epoll網上介紹的比較多,你們能夠看看這篇介紹的不錯的文章:我讀過的最好的epoll講解。通訊的一方想要知道另外一方的狀態(以決定本身作什麼),有兩種方法: 一是輪詢,二是消息通知。程序員
輪詢
輪詢的一種典型的實現多是這樣的:固然這裏的epoll_wait()也可使用poll()或者select()替換。算法
while (true) { 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之間的區別總結:https://www.cnblogs.com/Anker/p/3265058.html?spm=ata.13261165.0.0.4ec468f3ruw05F
有了上面的原理介紹,這裏舉例來講明下epoll究竟是怎麼使用的,加深理解。舉兩個例子:
一個是比較簡單的父子進程通訊的例子,單個小程序,不須要跑多個應用實例,不須要用戶輸入。https://www.cnblogs.com/goya/p/11925954.html
一個是比較實戰的socket+epoll,畢竟現實案例中哪有兩個父子進程間通信這麼簡單的應用場景。
有了多路複用,難道還不夠?
有了I/O複用,有了epoll已經可使服務器併發幾十萬鏈接的同時,維持高TPS了,難道這還不夠嗎?答案是,技術層面足夠了,但在軟件工程層面倒是不夠的。例如,總要有個for循環去調用epoll,總來處理epoll的返回,這是每次都要重複的工做。for循環體裏面寫什麼----通知返回以後,作事情的程序最好能以一種回調的機制,提供一個編程框架,讓程序更有結構一些。另外一方面,若是但願每一個事件通知以後,作的事情能有機會被代理到某個線程裏面去單獨運行,而線程完成的狀態又能通知回主任務,那麼"異步"的進制就必須被引入。
因此,還有兩個問題要解決,一是"編程框架",一是"異步"。咱們先看幾個目前流行的框架,大部分框架已經包含了某種異步的機制。咱們接下來的篇章將介紹「編程框架」和「異步I/O模型」。
原文出處:https://www.cnblogs.com/goya/p/11923335.html