I/O復用模型
多路復用是指使用一個線程來檢查多個文件描述符(Socket)的就緒狀態,比如調用select和poll函數,傳入多個文件描述符(FileDescription,簡稱FD),如果有一個文件描述符(FileDescription)就緒,則返回,否則阻塞直到超時。得到就緒狀態后進行真正的操作可以在同一個線程里執行,也可以啟動線程執行(比如使用線程池)。
目前支持IO多路復用的系統調用有select,pselect,poll,epoll。IO多路復用是一種機制,一個進程監控多個描述符,一旦某個描述符準備就緒(一般是讀就緒或者寫就緒),通知應用進行相應的IO操作(讀寫)。但是select、pselect、poll、epoll本質上都是同步IO,它們都是要在讀寫事件就緒后自己負責讀寫,也就是說這個讀寫過程是阻塞的。而異步IO是無需自己負責讀寫,把所有的操作全部給內核來處理。系統內核負責把數據從內核復制到用戶空間,在這個過程中,進程全程沒有阻塞。
與多進程和線程的技術相比,I/O多路復用技術最大的優勢就是系統開銷小,系統不必創建進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。
一、使用場景
IO多路復用指的是內核一旦發現一個或者多個IO條件準備讀取,就通知該進程。IO多路復用適用如下場合。
- 當客戶處理多個IO描述符時(一般是交互式輸入和網絡套接口),必須使用IO復用。
- 當一個客戶同時處理多個套接口時,這種情況是可能的,但很少出現。
- 如果一個TCP既要處理監聽套接字,又要處理已連接的套接字,一般也要用到I/O復用。
- 一個服務器既要處理TCP,又要處理UDP,一般也要使用I/O復用。
- 如果一個服務器要處理多個服務或者多個協議,一般也要使用I/O復用。
二、select、poll、epoll簡介
epoll跟select都能提供多路IO復用的解決方案。在現在的Linux內核里有都能夠支持,其中epoll是Linux特有的,而select是POSIX所規定,一般操作系統均有實現。
1. select
基本原理:select函數監視的文件描述符分為3類,分別為write_fds、read_fds、except_fds。調用select函數后,進程會阻塞,直到描述符就緒(有數據 可讀、可寫、或者有except),或者超時(timeout指定等待時間,如果立即返回設為null即可),函數返回。當select函數返回后,可以通過遍歷fd_set,來找到就緒的描述符。
-
基本流程 ,如圖所示
image.png
select基本上在所有的平臺上都支持,其良好跨平臺支持也是它的一個優點。select的一個缺點就是單個進程能夠監視的文件描述符的數量存在最大的限制,Linux上一般都是1024,可以修改宏定義來提升這一限制(內核需要重新的編譯)。
select的本質是通過設置或者檢查存放fd標志位的數據結構來進一步的處理。下面是select的缺點。 - select的最大缺點是單個文件打開的FD有限制,由FD_SETSIZE設置,一般是1024。
- 對socket進行掃描時是線性掃描,即采用輪詢的方法,效率低。
- 每次調用 select(),都需要把 fd 集合從用戶態拷貝到內核態,這個開銷在 fd 很多時會很大,同時每次調用 select() 都需要在內核遍歷傳遞進來的所有 fd,這個開銷在 fd 很多時也很大。
2. poll
select() 和 poll() 系統調用的本質一樣,poll() 的機制與 select() 類似,與 select() 在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是 poll() 沒有最大文件描述符數量的限制(但是數量過大后性能也是會下降)。poll() 和 select() 同樣存在一個缺點就是,包含大量文件描述符的數組被整體復制于用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數量的增加而線性增大。
注意:select和poll都需要在返回后,通過遍歷文件描述符來獲取已經就緒的文件描述符。如果只有很少的文件描述符處于就緒狀態,那么隨著監視的描述符數量的增長,其效率也會線性下降。
3.epoll
epoll是在2.6內核中提出的,是之前的select和poll的增強版本。相對于select和poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。
epoll的優點:
- 沒有最大并發連接的限制,能打開的FD的上限遠大于1024(1G的內存上能監聽約10萬個端口)。
- 效率提升,不是輪詢的方式,不會隨著FD數目的增加效率下降。
只有活躍可用的FD才會調用callback函數;即epoll最大的優點就在于它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,epoll的效率就會遠遠高于select和poll。
- 內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少復制開銷。
epoll的操作模式
epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認模式,LT模式與ET模式的區別如下:
- LT模式:當epoll_wait檢測到描述符事件發生并將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用epoll_wait時,會再次響應應用程序并通知此事件。
LT(level triggered)是缺省的工作方式,并且同時支持block和no-block socket。在這種做法中,內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你的。 - ET模式:當epoll_wait檢測到描述符事件發生并將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用epoll_wait時,不會再次響應應用程序并通知此事件。
ET(edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變為就緒時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經就緒,并且不會再為那個文件描述符發送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再為就緒狀態了(比如,你在發送,接收或者接收請求,或者發送接收的數據少于一定量時導致了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once)。
ET模式在很大程度上減少了epoll事件被重復觸發的次數,因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。
4.select、poll、epoll
在select/poll中,進程只有在調用一定的方法后,內核才對所有監視的文件描述符進行掃描,而epoll事件先通過epoll_ctl()來注冊一個文件描述符,一旦基于某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。(此處去掉了遍歷文件描述符,而是通過監聽回調的的機制。這正是epoll的魅力所在。)
三、select、poll、epoll區別
-
支持一個進程所能打開的最大連接數
image.png -
FD劇增后帶來的IO效率問題
image.png -
消息傳遞方式
image.png
綜上,在選擇select,poll,epoll時要根據具體的使用場合以及這三種方式的自身特點:
1、表面上看epoll的性能最好,但是在連接數少并且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制需要很多函數回調。
2、select低效是因為每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善。
四、面試可回答區別
select
- select能監控的描述符個數由內核中的FD_SETSIZE限制,僅為1024,這也是select最大的缺點,因為現在的服務器并發量遠遠不止1024。即使能重新編譯內核改變FD_SETSIZE的值,但這并不能提高select的性能。
- 每次調用select都會線性掃描所有描述符的狀態,在select結束后,用戶也要線性掃描fd_set數組才知道哪些描述符準備就緒,等于說每次調用復雜度都是O(n)的,在并發量大的情況下,每次掃描都是相當耗時的,很有可能有未處理的連接等待超時。
- 每次調用select都要在用戶空間和內核空間里進行內存復制fd描述符等信息。
poll
- poll使用pollfd結構來存儲fd,突破了select中描述符數目的限制。
- 與select的后兩點類似,poll仍然需要將pollfd數組拷貝到內核空間,之后依次掃描fd的狀態,整體復雜度依然是O(n)的,在并發量大的情況下服務器性能會快速下降。
epoll
- epoll維護的描述符數目不受到限制,而且性能不會隨著描述符數目的增加而下降。
- 服務器的特點是經常維護著大量連接,但其中某一時刻讀寫的操作符數量卻不多。epoll先通過epoll_ctl注冊一個描述符到內核中,并一直維護著而不像poll每次操作都將所有要監控的描述符傳遞給內核;在描述符讀寫就緒時,通過回掉函數將自己加入就緒隊列中,之后epoll_wait返回該就緒隊列。也就是說,epoll基本不做無用的操作,時間復雜度僅與活躍的客戶端數有關,而不會隨著描述符數目的增加而下降。
- epoll在傳遞內核與用戶空間的消息時使用了內存共享,而不是內存拷貝,這也使得epoll的效率比poll和select更高。
參考自
https://www.cnblogs.com/jeakeven/p/5435916.html
https://www.nowcoder.com/questionTerminal/c162e1e930a34ea3ad6c8863ccff0fa2
https://blog.csdn.net/y396397735/article/details/55004775
https://www.cnblogs.com/ccsccs/articles/4224253.html (重點分析select)
https://blog.csdn.net/apacat/article/details/51375950 (這個也比較詳細)
https://blog.csdn.net/yusiguyuan/article/details/15027821
https://blog.csdn.net/mmz_xiaokong/article/details/8704988
https://www.cnblogs.com/Anker/p/3265058.html (區別整理)
https://blog.csdn.net/men_wen/article/details/53456491 (最好的例子)
https://www.cnblogs.com/zhuwbox/p/4221934.html
https://blog.csdn.net/tennysonsky/article/details/45745887/ (配合了代碼的分析)