這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Select
IO複用模型是上個世紀90年代的東西,受限於當時的電腦硬軟體的限制,這種技術隨著epoll的出現逐漸被取代,但它畢竟風光過。瞭解曆史才能更好的展望未來,每一個有情懷的碼農都不應該一味抬頭看遠方,時而低頭凝視大地,不亦樂乎~
瞭解select
之前,我們需要瞭解下位元影像(bitmap),bitmap
其實就是將對象映射到具體的一個bit
位上來,表示對象存在或者被標記。bitmap
演算法有節省記憶體和快速查詢等特點,所以適合處理海量資料的排序和查詢。這種古老而牛逼的技術在資料庫,作業系統上都有很廣泛的應用。好的,下面引入select
中使用到bitmap
演算法的幾個API函數,也是在使用select
這種IO複用技術時經常使用到的。
int FD_ZERO(fd_set *fdset); // 複位int FD_CLR(int fd, fd_set *fdset); // 清零int FD_SET(int fd, fd_set *fd_set); // 設定int FD_ISSET(int fd, fd_set *fdset); // 測試設定
這幾個函數主要完成具體 fd
到 fd_set rset
映射關係的處理。看下fd_set
的儲存結構:
#ifndef FD_SETSIZE#define FD_SETSIZE 1024#endif#define NBBY 8 /* number of bits in a byte */typedef long fd_mask;#define NFDBITS (sizeof (fd_mask) * NBBY) /* bits per mask */#define howmany(x,y) (((x)+((y)-1))/(y))typedef struct _types_fd_set { fd_mask fds_bits[howmany(FD_SETSIZE, NFDBITS)];} _types_fd_set;
清楚的看到,一個long
類型8個位元組,這樣fds_bits
就有1024/64 = 16
即16個64bit的數組,每一個數組64bit。設定和清零這兩個操作是位操作,很方便,自己寫了一個BitMap
的Golang
代碼,這裡也順便貼出:
func (b *BitSet) Set(i uint) { .... b.set[i>>6] = b.set[i>>6] | (1 << (i & (64 - 1)))}func (b *BitSet) Clear(i uint) { .... b.set[i>>6] &^= 1 << (i & (64 - 1))}
好了,知道fdset的儲存結構和簡單設定之後,可以看下select
IO模型中的另外一個API:
int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
其中有兩個參數需要說明下:
- 第一個參數是timeout, 它代表select逾時時間。該值有三種狀態:timeout == NULL 無條件等待。 select函數將返回 -1, erro設為 EINTR,表示被迫中斷。timeout->tv_sec == 0 &&timeout->tv_usec == 0 不等待,直接返回。timeout->tv_sec != 0 || timeout->tv_usec != 0 等待指定的時間,select返回0表示在規定時間內沒有fd讀寫或者例外狀況事件發生。
struct timeval { long tv_sec; /*秒 */ long tv_usec; /*微秒 */ }
- 第二個參數 maxfdp,這個是檔案描述符fd的最大值加1。每次調用
select
之前都需要算出最大的fd,作為maxfd。
關於select
的核心實現部分,網上有很多文章進行了詳細的描述,這裡就不再贅述。好的,這裡還是不落俗套地提下select的缺點:<1> 描述符(FD)數量問題 。<2> IO效率隨FD的增加而線性下降。select
隨FD的增加效能下降的問題, 在使用方式上可以感受到:使用者態需要每次select
之前複位所有fd,然後select
之後還得遍曆所有fd
找到可讀寫或異常的fd
。但至今沒有對select
做過benchmark
。
這裡隨便帶上poll
小弟:
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
其中pollfd的資料結構:
struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ short revents; /* returned events witnessed */};
poll方式並沒有採用select
的fdset
方式,而是每一個fd
都有一個自己的pollfd
資料結構,裡面存放除了fd
外,還存放events
事件類型,這樣poll
就沒有fd
個數的上限問題了,但它仍然需要遍曆fds
拿到可讀寫或異常的fd
。這點跟select
還是一樣的。
好了,小小書童簡單記錄下,end~