IO 多路復用 select vs pool vs epool
前言
- 在類 unix linux 系統(tǒng)中每一個進程都存在一個文件描述符表拾徙,表指向具體的文件胆屿,比如 socket, 設備(devices), 和其他操作系統(tǒng)對象蒿褂。
IO多路復用(IO Multiplexing )出現(xiàn)的背景
- 多個io資源協(xié)同工作的系統(tǒng)潘拨,具有典型的兩個階段绩衷,初始化階段蹦魔,然后進入等待模式激率,等待客戶端發(fā)起請求并對其進行相應。
- 簡單的實現(xiàn)是對每一個客戶端socket 請求創(chuàng)建一個thread 線程勿决,在read 的時候block 阻塞乒躺,直至一個請求被發(fā)送并且接受到一個寫入(write)的響應。
- 這種工作模式在客戶端client 比較少的時候是ok 的低缩, 但是如果在大規(guī)模的客戶端請求調(diào)度中嘉冒,為每個client 都創(chuàng)建一個線程是非常不好的。
io 多路復用應運而生咆繁。
IO Multiplexing 模式的支持
實現(xiàn)思想是: 采用內(nèi)核機制來輪詢一組文件描述符讳推,在linux 中有以下三種支持。
它們的實現(xiàn)思路一樣玩般,都是創(chuàng)建一組標志讀(read) 寫(write)的文件描述符银觅,并告知內(nèi)核,并用一個線程阻塞一個函數(shù)調(diào)用坏为,直到一個文件描述符的操作(read/write)可用究驴。
- select()
- pool()
- epool()
select() 系統(tǒng)調(diào)用
select () 指令
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
對一個select 指令的調(diào)用會被阻塞,直到給定的文件描述符執(zhí)行IO 操作匀伏,或則在指定的過期時間內(nèi)失效洒忧,被監(jiān)視的文件描述符分為三組。
- readfds: 監(jiān)視 readfds集中列出的文件描述符够颠,以查看是否有數(shù)據(jù)可讀取熙侍。
- writefds: 監(jiān)視 writefds 集中列出的文件描述符,已查看是否寫入操作完成且沒有發(fā)生阻塞摧找。
- exceptfds : 監(jiān)視是否出現(xiàn)異常核行,或則帶外數(shù)據(jù)(OOB)可用。
select 沒有監(jiān)察到上述事件就會返回null蹬耘。成功返回則修改每個集合芝雪。使其僅包含已經(jīng)準備就緒的文件描述符。
因為需要告知 select() 最大的文件描述符編號综苔,這是fd_sets 的內(nèi)部實現(xiàn)惩系。
【在fd_set 中 一個fd(file description) 文件描述占用 1bit, fd_set 是length 為32 的整數(shù)數(shù)組,(32 x 4byte x 8bit = 1024bit) 如筛,假設存在 8個文件描述符堡牡,最大的文件描述符值為1000, 那么將會在0 ~ 1000 中找到監(jiān)視的文件描述符】
這也是一個弊端杨刨,因為需要在每次輪詢迭代時重新構建文件描述符集晤柄。
select () 總結
- 在每次調(diào)用前需要重新構建每個fd_set 集合。
- 函數(shù)調(diào)用監(jiān)視文件描述符最大值N 范圍內(nèi)的任何位妖胀,時間復雜度為 O(N)芥颈。
- 需要輪詢遍歷文件描述符集惠勒,檢查是否存在于從 select() 返回的集合中。
- select 最大的優(yōu)勢是輕便爬坑,可移植纠屋,任何unix 系統(tǒng)都支持。
pool() 系統(tǒng)調(diào)用
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
與select () 采用3個 基于標記位的文件描述符集不同盾计,pool 采用一個pollfd 結構售担,原型更簡單。
struct pollfd {
int fd;
short events;
short revents;
};
為每一個文件描述符構建一個 pollfd 類型的兌對象署辉,并填充請求(request)事件族铆,然后輪詢返回響應,檢查返回事件(revents)字段涨薪。
與selelct() 類似骑素,需要檢測每一個pollfd 對象去看它的文件描述符是否已準備好,刚夺,但是不需要在每次輪詢時重新構建文件描述符集献丑。
epool() 系統(tǒng)調(diào)用
pool() ,select() 工作時,我們需要在用戶空間管理任何事情侠姑,在每次調(diào)用發(fā)送 文件描述符集時陷入阻塞等待创橄。添加另外的socket , 我們需要把它加入到 集合中并再一次調(diào)用 pool() select()莽红。
epool() 幫助我們在內(nèi)核中創(chuàng)建和管理上下文妥畏,可以分為以下三步:
- 調(diào)用 epool_create 在內(nèi)核中創(chuàng)建上下文。
- 調(diào)用epool_ctl 在上線文中添加或刪除文件描述符安吁。
- 采用epool_wait 等待上下文中的事件醉蚁。
pool() vs select()
- pool() 不需要用戶去計算文件描述符最大數(shù)+1。
- pool() 對于很大數(shù)值的文件描述符更有效率鬼店。
- select() 的文件描述符集是固定的大小网棍。
- 采用select() 系統(tǒng)調(diào)用,文件描述符集在返回后被重新構造妇智,因此接下來的調(diào)用必須重新實例化滥玷,pool() 將輸入 (event 字段)和 輸出 (output )分開,從而允許文件描述符集可以重用而無需改動巍棱。
- select() 更輕便惑畴, 一些unix 系統(tǒng)不支持 pool()。
epool vs pool vs select
- 在等待io 響應的時候可以添加或刪除文件描述符航徙。
- epool_wait 僅返回具有已經(jīng)準備的文件描述符的對象如贷。
- epool 擁有更高的性能,時間復雜度為 O(1)。
- epool 可以表現(xiàn)為水平觸發(fā)杠袱,和邊緣觸發(fā)泻红。
- epool 是linux 特有的,不具有很好的可移植性霞掺。