作者:羅志宇
鏈接:https://www.zhihu.com/question/32163005/answer/55772739
來(lái)源:知乎
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
這個(gè)還是很好說(shuō)清楚的。
假設(shè)你是一個(gè)機(jī)場(chǎng)的空管, 你需要管理到你機(jī)場(chǎng)的所有的航線(xiàn), 包括進(jìn)港,出港, 有些航班需要放到停機(jī)坪等待,有些航班需要去登機(jī)口接乘客。
你會(huì)怎么做?
最簡(jiǎn)單的做法,就是你去招一大批空管員,然后每人盯一架飛機(jī), 從進(jìn)港,接客,排位,出港,航線(xiàn)監(jiān)控,直至交接給下一個(gè)空港,全程監(jiān)控。
那么問(wèn)題就來(lái)了:
很快你就發(fā)現(xiàn)空管塔里面聚集起來(lái)一大票的空管員,交通稍微繁忙一點(diǎn),新的空管員就已經(jīng)擠不進(jìn)來(lái)了。
空管員之間需要協(xié)調(diào),屋子里面就1, 2個(gè)人的時(shí)候還好,幾十號(hào)人以后 ,基本上就成菜市場(chǎng)了。
空管員經(jīng)常需要更新一些公用的東西,比如起飛顯示屏,比如下一個(gè)小時(shí)后的出港排期,最后你會(huì)很驚奇的發(fā)現(xiàn),每個(gè)人的時(shí)間最后都花在了搶這些資源上。
現(xiàn)實(shí)上我們的空管同時(shí)管幾十架飛機(jī)稀松平常的事情, 他們?cè)趺醋龅哪兀?/p>
他們用這個(gè)東西

這個(gè)東西叫flight progress strip.? 每一個(gè)塊代表一個(gè)航班,不同的槽代表不同的狀態(tài),然后一個(gè)空管員可以管理一組這樣的塊(一組航班),而他的工作,就是在航班信息有新的更新的時(shí)候,把對(duì)應(yīng)的塊放到不同的槽子里面。
這個(gè)東西現(xiàn)在還沒(méi)有淘汰哦,只是變成電子的了而已。。
是不是覺(jué)得一下子效率高了很多,一個(gè)空管塔里可以調(diào)度的航線(xiàn)可以是前一種方法的幾倍到幾十倍。
如果你把每一個(gè)航線(xiàn)當(dāng)成一個(gè)Sock(I/O 流),? 空管當(dāng)成你的服務(wù)端Sock管理代碼的話(huà).
第一種方法就是最傳統(tǒng)的多進(jìn)程并發(fā)模型 (每進(jìn)來(lái)一個(gè)新的I/O流會(huì)分配一個(gè)新的進(jìn)程管理。)
第二種方法就是I/O多路復(fù)用 (單個(gè)線(xiàn)程,通過(guò)記錄跟蹤每個(gè)I/O流(sock)的狀態(tài),來(lái)同時(shí)管理多個(gè)I/O流 。)
其實(shí)“I/O多路復(fù)用”這個(gè)坑爹翻譯可能是這個(gè)概念在中文里面如此難理解的原因。所謂的I/O多路復(fù)用在英文中其實(shí)叫 I/O multiplexing. 如果你搜索multiplexing啥意思,基本上都會(huì)出這個(gè)圖:

于是大部分人都直接聯(lián)想到"一根網(wǎng)線(xiàn),多個(gè)sock復(fù)用" 這個(gè)概念,包括上面的幾個(gè)回答, 其實(shí)不管你用多進(jìn)程還是I/O多路復(fù)用, 網(wǎng)線(xiàn)都只有一根好伐。多個(gè)Sock復(fù)用一根網(wǎng)線(xiàn)這個(gè)功能是在內(nèi)核+驅(qū)動(dòng)層實(shí)現(xiàn)的。
重要的事情再說(shuō)一遍: I/O multiplexing 這里面的 multiplexing 指的其實(shí)是在單個(gè)線(xiàn)程通過(guò)記錄跟蹤每一個(gè)Sock(I/O流)的狀態(tài)(對(duì)應(yīng)空管塔里面的Fight progress strip槽)來(lái)同時(shí)管理多個(gè)I/O流. 發(fā)明它的原因,是盡量多的提高服務(wù)器的吞吐能力。
是不是聽(tīng)起來(lái)好拗口,看個(gè)圖就懂了.

在同一個(gè)線(xiàn)程里面, 通過(guò)撥開(kāi)關(guān)的方式,來(lái)同時(shí)傳輸多個(gè)I/O流, (學(xué)過(guò)EE的人現(xiàn)在可以站出來(lái)義正嚴(yán)辭說(shuō)這個(gè)叫“時(shí)分復(fù)用”了)。
什么,你還沒(méi)有搞懂“一個(gè)請(qǐng)求到來(lái)了,nginx使用epoll接收請(qǐng)求的過(guò)程是怎樣的”, 多看看這個(gè)圖就了解了。提醒下,ngnix會(huì)有很多鏈接進(jìn)來(lái), epoll會(huì)把他們都監(jiān)視起來(lái),然后像撥開(kāi)關(guān)一樣,誰(shuí)有數(shù)據(jù)就撥向誰(shuí),然后調(diào)用相應(yīng)的代碼處理。
------------------------------------------
了解這個(gè)基本的概念以后,其他的就很好解釋了。
select, poll, epoll 都是I/O多路復(fù)用的具體的實(shí)現(xiàn),之所以有這三個(gè)鬼存在,其實(shí)是他們出現(xiàn)是有先后順序的。
I/O多路復(fù)用這個(gè)概念被提出來(lái)以后, select是第一個(gè)實(shí)現(xiàn) (1983 左右在BSD里面實(shí)現(xiàn)的)。
select 被實(shí)現(xiàn)以后,很快就暴露出了很多問(wèn)題。
select 會(huì)修改傳入的參數(shù)數(shù)組,這個(gè)對(duì)于一個(gè)需要調(diào)用很多次的函數(shù),是非常不友好的。
select 如果任何一個(gè)sock(I/O stream)出現(xiàn)了數(shù)據(jù),select 僅僅會(huì)返回,但是并不會(huì)告訴你是那個(gè)sock上有數(shù)據(jù),于是你只能自己一個(gè)一個(gè)的找,10幾個(gè)sock可能還好,要是幾萬(wàn)的sock每次都找一遍,這個(gè)無(wú)謂的開(kāi)銷(xiāo)就頗有海天盛筵的豪氣了。
select 只能監(jiān)視1024個(gè)鏈接, 這個(gè)跟草榴沒(méi)啥關(guān)系哦,linux 定義在頭文件中的,參見(jiàn)FD_SETSIZE。
select 不是線(xiàn)程安全的,如果你把一個(gè)sock加入到select, 然后突然另外一個(gè)線(xiàn)程發(fā)現(xiàn),尼瑪,這個(gè)sock不用,要收回。對(duì)不起,這個(gè)select 不支持的,如果你喪心病狂的竟然關(guān)掉這個(gè)sock, select的標(biāo)準(zhǔn)行為是。。呃。。不可預(yù)測(cè)的, 這個(gè)可是寫(xiě)在文檔中的哦.
“If a file descriptor being monitored by select() is closed in another thread, the result is? ? unspecified”
霸不霸氣
于是14年以后(1997年)一幫人又實(shí)現(xiàn)了poll,? poll 修復(fù)了select的很多問(wèn)題,比如
poll 去掉了1024個(gè)鏈接的限制,于是要多少鏈接呢, 主人你開(kāi)心就好。
poll 從設(shè)計(jì)上來(lái)說(shuō),不再修改傳入數(shù)組,不過(guò)這個(gè)要看你的平臺(tái)了,所以行走江湖,還是小心為妙。
其實(shí)拖14年那么久也不是效率問(wèn)題, 而是那個(gè)時(shí)代的硬件實(shí)在太弱,一臺(tái)服務(wù)器處理1千多個(gè)鏈接簡(jiǎn)直就是神一樣的存在了,select很長(zhǎng)段時(shí)間已經(jīng)滿(mǎn)足需求。
但是poll仍然不是線(xiàn)程安全的, 這就意味著,不管服務(wù)器有多強(qiáng)悍,你也只能在一個(gè)線(xiàn)程里面處理一組I/O流。你當(dāng)然可以那多進(jìn)程來(lái)配合了,不過(guò)然后你就有了多進(jìn)程的各種問(wèn)題。
于是5年以后, 在2002, 大神 Davide Libenzi 實(shí)現(xiàn)了epoll.
epoll 可以說(shuō)是I/O 多路復(fù)用最新的一個(gè)實(shí)現(xiàn),epoll 修復(fù)了poll 和select絕大部分問(wèn)題, 比如:
epoll 現(xiàn)在是線(xiàn)程安全的。
epoll 現(xiàn)在不僅告訴你sock組里面數(shù)據(jù),還會(huì)告訴你具體哪個(gè)sock有數(shù)據(jù),你不用自己去找了。
epoll 當(dāng)年的patch,現(xiàn)在還在,下面鏈接可以看得到:
貼一張霸氣的圖,看看當(dāng)年神一樣的性能(測(cè)試代碼都是死鏈了, 如果有人可以刨墳找出來(lái),可以研究下細(xì)節(jié)怎么測(cè)的).

橫軸Dead connections 就是鏈接數(shù)的意思,叫這個(gè)名字只是它的測(cè)試工具叫deadcon. 縱軸是每秒處理請(qǐng)求的數(shù)量,你可以看到,epoll每秒處理請(qǐng)求的數(shù)量基本不會(huì)隨著鏈接變多而下降的。poll 和/dev/poll 就很慘了。
可是epoll 有個(gè)致命的缺點(diǎn)。。只有l(wèi)inux支持。比如BSD上面對(duì)應(yīng)的實(shí)現(xiàn)是kqueue。
其實(shí)有些國(guó)內(nèi)知名廠(chǎng)商把epoll從安卓里面裁掉這種腦殘的事情我會(huì)主動(dòng)告訴你嘛。什么,你說(shuō)沒(méi)人用安卓做服務(wù)器,尼瑪你是看不起p2p軟件了啦。
而ngnix 的設(shè)計(jì)原則里面, 它會(huì)使用目標(biāo)平臺(tái)上面最高效的I/O多路復(fù)用模型咯,所以才會(huì)有這個(gè)設(shè)置。一般情況下,如果可能的話(huà),盡量都用epoll/kqueue吧。
詳細(xì)的在這里:
PS: 上面所有這些比較分析,都建立在大并發(fā)下面,如果你的并發(fā)數(shù)太少,用哪個(gè),其實(shí)都沒(méi)有區(qū)別。 如果像是在歐朋數(shù)據(jù)中心里面的轉(zhuǎn)碼服務(wù)器那種動(dòng)不動(dòng)就是幾萬(wàn)幾十萬(wàn)的并發(fā),不用epoll我可以直接去撞墻了。