在讀redis源代碼的過程當中,我一直在考慮一個問題,就是「爲何單線程的redis能作到如此高效?」。爲了弄清楚這個問題,我查閱了一些資料,大概搞清楚了epoll等I/O模型的發展及其原理,如下是一個記錄整理。html
##I/O模型node
###操做系統與網絡I/Oreact
上圖來自維基百科,是一個基本的計算機結構。計算機主要完成兩個工做,運算和I/O。由於CPU處理效率以及各類設備的I/O效率千差萬別,因此協調I/O提升效率是操做系統的一個重要任務。各類I/O模型應運而生。linux
####同步和異步git
計算機任務執行的實體是進程,進程和I/O的關係須要理清。根據執行I/O時進程的狀態,能夠把I/O操做分爲同步和異步兩種方式。github
* 同步IO操做:引發進程的阻塞直到IO操做完成redis
* 異步IO操做:IO操做不會引發進程阻塞
編程
其中同步和異步指進程和IO的關係,阻塞和非阻塞指進程的狀態。結合操做系統的進程管理,能夠認爲最優的狀態是:a)進程徹底不被影響,繼續執行直到I/O完成再處理 b)進程徹底掛起,把資源交給其餘進程,I/O完成後再醒來繼續執行。服務器
###網絡IO模型網絡
####阻塞IO
最基礎的方式,問題在於進程阻塞於一個IO就不能響應其餘請求,效率很低。
####非阻塞IO
使用輪詢的方式,會佔用大量的計算資源,應該只有特殊狀況纔會使用到。
####IO複用
關鍵在於複用,一個進程能夠一次性等待多個IO。
####信號驅動IO
使用範圍很小,在TCP下信號產生的過於頻繁難以區分含義,因此只在使用UDP協議的程序中應用。「做者能找到的實際使用信號驅動的I/O程序是基於UDP的NTP服務器程序」。與異步IO的理念區別僅僅在因而否由操做系統自動拷貝數據到內核空間,我不明白爲何不直接發展成異步IO,反而要加入這個模型。
####異步IO
整個過程進程都是非阻塞的。
前4種都是同步的,第5種是異步
##socket
###socket基本概念
socket工做在會話層以上,經過綁定並監聽指定端口,接收數據。
###socket示例
最簡單的accept()方式是阻塞的,在創建鏈接以前程序掛起。能夠使用while循環accept,不過這種方式read()也是阻塞的,因此在第一個鏈接結束以前,第二個鏈接沒法被處理。
寫代碼中的兩個有趣的點:
* listen函數的backlog有一個magic number 511
* close()以後有一個time_wait的過程,這時候若是再綁定相同端口會失敗,能夠使用setsockopt()
##改進
###使用多進程改進
在while循環中,每次accept就fork出一個新進程,這樣就能同時處理多個鏈接。這裏須要處理父進程與子進程的關係,引用計數,信號處理等問題。並且每次建立銷燬進程消耗較大。
###使用select改進
在while循環中,使用select監聽一個fd集合,當其中的fd可讀/寫/異常時從阻塞恢復。能夠同時監聽多個fd。固然讀寫也會阻塞,因此要配合多進程/多線程使用。由於不須要每一個鏈接就生成一個新進程,比fork的方式要更優化。
存在的問題是:
* fd數量有限
* 每次都循環檢查全部fd效率低
* 用戶態和內核態內存拷貝效率低
###使用epoll改進
epoll針對select的問題作了優化:
* 上限是最大能夠打開文件的數目
* 只關注「活躍」的連接不用循環檢查
* 使用內存共享避免拷貝
使用epoll 主要調用3個API :
int epoll_create(int size); //2.6.8以後size參數被忽略,參考
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
另外特別須要注意的是水平觸發(LT)和邊緣觸發(ET)的概念,以及events的處理(出錯,client關閉等等)。
以上幾種方式我都簡單實現了,代碼見【20】。
###性能對比測試
這篇論文【18】 對select/poll/epoll的性能作了對比測試。能夠看出在全部I/O都活躍的狀況下,select和epoll的性能相近。但存在大量空閒鏈接時,epoll的性能就明顯高於select了,這與epoll的改進思路是相符合的。
在查找資料的過程當中,我發現大部分資料都語焉不詳或者思路不清晰。因此把IO和socket做爲前置知識,結合了一些代碼示例。但願應該能更好的理清思路,理解I/O模型爲何要這樣設計和實際應用中爲何要這樣改進。寫的東西很少,乾貨都在參考文章中。
參考
【1】Unix 五種基本I/O模型的區別 - 語行 - 博客園
【2】Operating Systems: I/O Systems
【3】OSI model - Wikipedia, the free encyclopedia
【4】簡單理解Socket - dolphinX - 博客園
【5】Linux Howtos: C/C++ -> Sockets Tutorial
【6】linux文件設備與I/O:read/write函數 與 阻塞 Block_麪包坊_百度空間
【7】socket編程-listen函數之backlog_飛翔的魚在北京_新浪博客
【8】[C/C++] 解決Socket連續Bind同一個Port的問題 | 不務正業紀實
【9】linux 多進程 缺點 - 網摘記錄 - ITeye技術網站
【10】The GNU C Library: Server Example
【11】Linux Epoll介紹和程序實例 - sparkliang的專欄 - 博客頻道 - CSDN.NET
【12】Linux下select, poll和epoll IO模型的詳解 - tianmo2010的專欄 - 博客頻道 - CSDN.NET
【13】UNIX網絡編程--I/O複用:select函數和poll函數講解(六) - 魚思故淵的專欄 - 博客頻道 - CSDN.NET
【14】epoll 或者 kqueue 的原理是什麼? - 知乎 Epoll detailed
【15】How to use epoll? A complete example in C - Banu Blog
【16】epoll(4): I/O event notification facility - Linux man page
【17】epoll_create(2): open epoll file descriptor - Linux man page
【18】https://www.kernel.org/doc/ols/2004/ols2004v1-pages-215-226.pdf
【19】networking - Caveats of select/poll vs. epoll reactors in Twisted - Stack Overflow