小記:
阻塞,非阻塞:進程/線程要訪問的數(shù)據(jù)是否就緒,進程/線程是否需要等待;
同步,異步:訪問數(shù)據(jù)的方式,同步需要主動讀寫數(shù)據(jù),在讀寫數(shù)據(jù)的過程中還是會阻塞;異步只需要I/O操作完成的通知,并不主動讀寫數(shù)據(jù),由操作系統(tǒng)內(nèi)核完成數(shù)據(jù)的讀寫。
Select IO復(fù)用模型是上個世紀90年代的東西,受限于當(dāng)時的計算機硬軟件的限制,這種技術(shù)隨著epoll的出現(xiàn)逐漸被取代,但它畢竟風(fēng)光過。了解歷史才能更好的展望未來,每一個有情懷的碼農(nóng)都不應(yīng)該一味抬頭看遠方,時而低頭凝視大地,不亦樂乎~
了解select之前,我們需要了解下位圖(bitmap),bitmap其實就是將對象映射到具體的一個bit位上來,表示對象存在或者被標記。bitmap算法有節(jié)省內(nèi)存和快速查詢等特點,所以適合處理海量數(shù)據(jù)的排序和查詢。這種古老而牛逼的技術(shù)在數(shù)據(jù)庫,操作系統(tǒng)上都有很廣泛的應(yīng)用。好的,下面引入select中使用到bitmap算法的幾個API函數(shù),也是在使用select這種IO復(fù)用技術(shù)時經(jīng)常使用到的。
int FD_ZERO(fd_set *fdset); // 復(fù)位
int FD_CLR(int fd, fd_set *fdset); // 清零
int FD_SET(int fd, fd_set *fd_set); // 設(shè)置
int FD_ISSET(int fd, fd_set *fdset); // 測試設(shè)置
這幾個函數(shù)主要完成具體 fd 到 fd_set rset 映射關(guān)系的處理??聪?code>fd_set的存儲結(jié)構(gòu):
#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個字節(jié),這樣fds_bits就有1024/64 = 16 即16個64bit的數(shù)組,每一個數(shù)組64bit。設(shè)置和清零這兩個操作是位操作,很方便,自己寫了一個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的存儲結(jié)構(gòu)和簡單設(shè)置之后,可以看下selectIO模型中的另外一個API:
int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
其中有兩個參數(shù)需要說明下:
- 第一個參數(shù)是timeout, 它代表select超時時間。該值有三種狀態(tài):timeout == NULL 無條件等待。 select函數(shù)將返回 -1, erro設(shè)為 EINTR,表示被迫中斷。timeout->tv_sec == 0 &&timeout->tv_usec == 0 不等待,直接返回。timeout->tv_sec != 0 || timeout->tv_usec != 0 等待指定的時間,select返回0表示在規(guī)定時間內(nèi)沒有fd讀寫或者異常事件發(fā)生。
struct timeval {
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
}
- 第二個參數(shù) maxfdp,這個是文件描述符fd的最大值加1。每次調(diào)用
select之前都需要算出最大的fd,作為maxfd。
關(guān)于select的內(nèi)核實現(xiàn)部分,網(wǎng)上有很多文章進行了詳細的描述,這里就不再贅述。好的,這里還是不落俗套地提下select的缺點:<1> 描述符(FD)數(shù)量問題 。<2> IO效率隨FD的增加而線性下降。select隨FD的增加性能下降的問題, 在使用方式上可以感受到:用戶態(tài)需要每次select之前復(fù)位所有fd,然后select之后還得遍歷所有fd找到可讀寫或異常的fd。但至今沒有對select做過benchmark。
這里隨便帶上poll小弟:
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
其中pollfd的數(shù)據(jù)結(jié)構(gòu):
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
poll方式并沒有采用select的fdset方式,而是每一個fd都有一個自己的pollfd數(shù)據(jù)結(jié)構(gòu),里面存放除了fd外,還存放events事件類型,這樣poll就沒有fd個數(shù)的上限問題了,但它仍然需要遍歷fds拿到可讀寫或異常的fd。這點跟select還是一樣的。
好了,小小書童簡單記錄下,end~