小記:
阻塞,非阻塞:進(jìn)程/線程要訪問的數(shù)據(jù)是否就緒椰于,進(jìn)程/線程是否需要等待勤婚;
同步呛伴,異步:訪問數(shù)據(jù)的方式褥民,同步需要主動讀寫數(shù)據(jù)季春,在讀寫數(shù)據(jù)的過程中還是會阻塞;異步只需要I/O操作完成的通知消返,并不主動讀寫數(shù)據(jù)载弄,由操作系統(tǒng)內(nèi)核完成數(shù)據(jù)的讀寫。
Select
IO復(fù)用模型是上個(gè)世紀(jì)90年代的東西侦副,受限于當(dāng)時(shí)的計(jì)算機(jī)硬軟件的限制,這種技術(shù)隨著epoll的出現(xiàn)逐漸被取代驼鞭,但它畢竟風(fēng)光過秦驯。了解歷史才能更好的展望未來,每一個(gè)有情懷的碼農(nóng)都不應(yīng)該一味抬頭看遠(yuǎn)方挣棕,時(shí)而低頭凝視大地译隘,不亦樂乎~
了解select
之前,我們需要了解下位圖(bitmap)洛心,bitmap
其實(shí)就是將對象映射到具體的一個(gè)bit
位上來固耘,表示對象存在或者被標(biāo)記。bitmap
算法有節(jié)省內(nèi)存和快速查詢等特點(diǎn)词身,所以適合處理海量數(shù)據(jù)的排序和查詢厅目。這種古老而牛逼的技術(shù)在數(shù)據(jù)庫,操作系統(tǒng)上都有很廣泛的應(yīng)用。好的损敷,下面引入select
中使用到bitmap
算法的幾個(gè)API函數(shù)葫笼,也是在使用select
這種IO復(fù)用技術(shù)時(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è)置
這幾個(gè)函數(shù)主要完成具體 fd
到 fd_set rset
映射關(guān)系的處理拗馒÷沸牵看下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;
清楚的看到,一個(gè)long
類型8個(gè)字節(jié)诱桂,這樣fds_bits
就有1024/64 = 16
即16個(gè)64bit的數(shù)組洋丐,每一個(gè)數(shù)組64bit。設(shè)置和清零這兩個(gè)操作是位操作挥等,很方便友绝,自己寫了一個(gè)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è)置之后九榔,可以看下select
IO模型中的另外一個(gè)API:
int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
其中有兩個(gè)參數(shù)需要說明下:
- 第一個(gè)參數(shù)是timeout, 它代表select超時(shí)時(shí)間。該值有三種狀態(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 等待指定的時(shí)間,select返回0表示在規(guī)定時(shí)間內(nèi)沒有fd讀寫或者異常事件發(fā)生丙号。
struct timeval {
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
}
- 第二個(gè)參數(shù) maxfdp先朦,這個(gè)是文件描述符fd的最大值加1。每次調(diào)用
select
之前都需要算出最大的fd犬缨,作為maxfd喳魏。
關(guān)于select
的內(nèi)核實(shí)現(xiàn)部分,網(wǎng)上有很多文章進(jìn)行了詳細(xì)的描述怀薛,這里就不再贅述刺彩。好的,這里還是不落俗套地提下select的缺點(diǎn):<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
方式,而是每一個(gè)fd
都有一個(gè)自己的pollfd
數(shù)據(jù)結(jié)構(gòu)十电,里面存放除了fd
外知押,還存放events
事件類型叹螟,這樣poll
就沒有fd
個(gè)數(shù)的上限問題了,但它仍然需要遍歷fds
拿到可讀寫或異常的fd
朗徊。這點(diǎn)跟select
還是一樣的首妖。
好了,小小書童簡單記錄下爷恳,end~