我覺得要理解一個概念,往往需要把幾個相近的概念拿到一起談啸罢,然后對比各種異同财著,這樣才能更好的理解耕皮,并且一通百通。
先談談select谊惭,首先select每次調(diào)用前需要進行FD_SET 關注多少描述符就需要調(diào)用多少次FD_SET汽馋,所以這個是o(n)的操作;其次每次select需要把描述符集合從用戶空間拷貝到內(nèi)核空間圈盔;再次在內(nèi)核中查詢的時候豹芯,select查詢又是o(n)的查詢,每次需遍歷所有的關注的描述符查詢是否滿足條件(可讀或者可寫)驱敲,然后返回給用戶空間铁蹈;最后select的描述符集合實現(xiàn)是數(shù)組,所以大小有一定的限制众眨。
epoll對fd的管理采用紅黑樹握牧,只有當fd真的增刪前才會調(diào)用epoll_ctl(對比select,epoll而不是每次epoll_wait前需要添加所有的fd娩梨,也不會每次epoll_wait前有所有描述符數(shù)據(jù)從用戶空間到內(nèi)核空間的拷貝)沿腰;其次紅黑樹增刪都是o(logN)的操作,比較高效狈定。最后當描述符準備好時矫俺,調(diào)用epoll_wait時,只需要返回滿足條件的描述符鏈表掸冤,這個本身是o(1)的操作。最后epoll本身也是線程安全的友雳,采用紅黑樹時稿湿,不論增刪,復雜度都是0(logN),由于紅黑樹的性質(zhì)押赊,增最多2次旋轉(zhuǎn)饺藤,刪最多3次旋轉(zhuǎn),其余都是染色的過程流礁,所以就算多線程訪問鎖的粒度也比較小涕俗,由于染色操作本身不影響查詢,只需旋轉(zhuǎn)時lock即可神帅。
epoll工作有2種模式再姑,水平觸發(fā)和邊緣觸發(fā),水平觸發(fā)關注的事件是socket層緩沖的可讀或者可寫字節(jié)數(shù)大于1即添加到返回鏈表中找御,邊緣觸發(fā)當其僅當socket層需要的緩沖從不可讀到可讀元镀,或者從不可寫到可寫才會添加到返回鏈表绍填。所以水平模式下,當返回鏈表中描述符返回給用戶的空間后栖疑,再次epoll_wait會在次檢查之前的鏈表是否真沒有關注的事件了讨永,否則再次加入鏈表中,這個相對來說就比較低效了遇革。在寫的場景下epoll需要關注的事件是EPOLLOUT卿闹,如果使用水平觸發(fā),每次寫入完畢后萝快,需要使用EPOLL_CTL_MOD關閉對EPOLLOUT事件的關注锻霎,這個也是相對邊緣觸發(fā)模式下的開銷,所以當寫入越頻繁杠巡,尤其在寫入一個大的數(shù)據(jù)包的時候量窘,由于協(xié)議窗口大小的限制無法一次寫完,這時性能差異就能體現(xiàn)出來氢拥。
還有一個概念不論epoll還是select都沒有進行io操作蚌铜,本質(zhì)都不算同步或者異步io的概念,獲取到關注的描述符信息后還要再次進行io操作嫩海,比如read/write冬殃。read/wirte本身還有一個數(shù)據(jù)從內(nèi)核空間和用戶空間的轉(zhuǎn)換的開銷。