1、基本概念
同步:同步函數一般指調用函數后,等到函數功能實現再返回,期間一直霸占的CPU,等待期間同一個線程無法執行其他函數
異步:異步函數指調用函數后,不管函數功能是否實現,立馬返回;通過回調函數等告知函數功能完成
阻塞:調用某些函數阻塞是因為函數功能沒有實現,主動放棄CPU,讓其他線程的得以執行;當功能實現后,函數返回
非阻塞:調用某些函數不會進入阻塞,無論實現與否,都會返回結果
2、5種 IO 模型
阻塞模型
應用程序調用一個IO函數,導致應用程序阻塞,等待數據准備好。 如果數據沒有准備好,一直等待
特點:
當希望能夠立即發送和接收數據,且處理的套接字數量比較少的情況下,使用阻塞模式來開發網絡程序比較合適。
阻塞模式套接字的不足表現為,在大量建立好的套接字線程之間進行通信時比較困難。當使用“生產者-消費者”模型開發網絡程序時,為每個套接字都分別分配一個讀線程、一個處理數據線程和一個用於同步的事件,那么這樣無疑加大系統的開銷。其最大的缺點是當希望同時處理大量套接字時,將無從下手,其擴展性很差
非阻塞模型
程序框架如下:
while true { for i in stream[] { if i has data read until unavailable } }
特點:
不像阻塞模型需要為每個socket創建一個線程,非阻塞只需要一個線程,但是會一直占用CPU
復用模型
I/O復用模型會用到select、poll、epoll函數,這幾個函數也會使進程阻塞(根據實參規定最長進入阻塞時間,是否進入阻塞),但是和阻塞I/O所不同的的,這些函數可以同時阻塞多個I/O操作。而且可以同時對多個讀操作、多個寫操作的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操作函數。
epoll跟select都能提供多路I/O復用的解決方案。在現在的Linux內核里有都能夠支持,其中epoll是Linux所特有,而select則應該是POSIX所規定,一般操作系統均有實現
select:
程序框架如下:
while true { select(streams[]) for i in streams[] { if i has data read until unavailable } }
select本質上是通過設置或者檢查存放fd標志位的數據結構來進行下一步處理。這樣所帶來的缺點是:
1、 單個進程可監視的fd數量被限制,即能監聽端口的大小有限。
一般來說這個數目和系統內存關系很大,具體數目可以cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.
2、 對socket進行掃描時是線性掃描,即采用輪詢的方法,效率較低:
因為select()只會告訴我們有套接字可讀或可寫,沒有指明是哪個套接字的哪個事件發生,所以需要遍歷每個套接字,時間復雜度是O(n)。如果能給套接字注冊某個回調函數,當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll與kqueue做的。
3、需要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時復制開銷大
poll:(有待重新組織語言)
poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然后查詢每個fd對應的設備狀態,如果設備就緒則在設備等待隊列中加入一項並繼續遍歷,如果遍歷完所有fd后沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒后它又要再次遍歷fd。這個過程經歷了多次無謂的遍歷。
它沒有最大連接數的限制,原因是它是基於鏈表來存儲的,但是同樣有一個缺點:
1、大量的fd的數組被整體復制於用戶態和內核地址空間之間,而不管這樣的復制是不是有意義。
2、poll還有一個特點是“水平觸發”,如果報告了fd后,沒有被處理,那么下次poll時會再次報告該fd。
epoll:
程序框架如下;
while true { active_stream[] = epoll_wait(epollfd) for i in active_stream[] { read or write till unavailable } }
epoll支持水平觸發和邊緣觸發,最大的特點在於邊緣觸發,它只告訴進程哪些fd剛剛變為就需態,並且只會通知一次。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,內核就會采用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知
epoll的優點:
2、效率提升,不是無差別輪詢的方式,不會隨着FD數目的增加效率下降。只有活躍可用的FD才會調用callback函數,時間復雜度為O(k),k為產生I/O事件的流的個數
即Epoll最大的優點就在於它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,Epoll的效率就會遠遠高於select和poll。
select、poll、epoll 區別總結:
1、一個進程所能打開的最大連接數
select |
單個進程所能打開的最大連接數由FD_SETSIZE宏定義,其大小是32個整數的大小(在32位的機器上,大小就是32*32,同理64位機器上FD_SETSIZE為32*64),當然我們可以對進行修改,然后重新編譯內核,但是性能可能會受到影響,這需要進一步的測試。 |
poll |
poll本質上和select沒有區別,但是它沒有最大連接數的限制,原因是它是基於鏈表來存儲的 |
epoll |
雖然連接數有上限,但是很大,1G內存的機器上可以打開10萬左右的連接,2G內存的機器可以打開20萬左右的連接 |
2、FD劇增后帶來的IO效率問題
select |
因為每次調用時都會對連接進行線性遍歷,所以隨着FD的增加會造成遍歷速度慢的“線性下降性能問題”。 |
poll |
同上 |
epoll |
因為epoll內核中實現是根據每個fd上的callback函數來實現的,只有活躍的socket才會主動調用callback,所以在活躍socket較少的情況下,使用epoll沒有前面兩者的線性下降的性能問題,但是所有socket都很活躍的情況下,可能會有性能問題。 |
3、 消息傳遞方式
select |
內核需要將消息傳遞到用戶空間,都需要內核拷貝動作 |
poll |
同上 |
epoll |
epoll通過內核和用戶空間共享一塊內存來實現的。 |
總結:
綜上,在選擇select,poll,epoll時要根據具體的使用場合以及這三種方式的自身特點。
1、表面上看epoll的性能最好,但是在連接數少並且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制需要很多函數回調。
2、select低效是因為每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善
3、epoll 能顯著提高程序在大量並發連接中只有少量活躍的情況下的系統CPU利用率
信號驅動模型
首先我們允許套接口進行信號驅動I/O,並安裝一個信號處理函數,進程繼續運行並不阻塞。當數據准備好時,進程會收到一個SIGIO信號,可以在信號處理函數中調用I/O操作函數處理數據。
異步模型
簡介:數據拷貝的時候進程無需阻塞。
當一個異步過程調用發出后,調用者不能立刻得到結果。實際處理這個調用的部件在完成后,通過狀態、通知和回調來通知調用者的輸入輸出操作
比較