舉個(gè)例子:
假設(shè)我們需要編寫一個(gè)echo服務(wù)器程序,功能是:
響應(yīng)用戶從標(biāo)準(zhǔn)輸入端鍵入的命令。接收網(wǎng)絡(luò)客戶端發(fā)起的連接請(qǐng)求。這其實(shí)是2個(gè)不同IO事件,該如何等待請(qǐng)求和處理命令呢?
若服務(wù)器在accept中一直等待網(wǎng)絡(luò)客戶端連接,就不能處理輸入命令,反過(guò)來(lái)在read中等待用戶鍵入命令,那就不能接受客戶端請(qǐng)求,你會(huì)說(shuō)我們可以使用多進(jìn)程或多線程,但我們希望不要引起多余的上下文切換,使用單線程來(lái)處理可以嗎?IO多路復(fù)用就是這種解決方法,基本思路是使用select函數(shù),該函數(shù)會(huì)一直掛起來(lái)監(jiān)聽(tīng)的輸入IO集合(文件描述符),只有在一個(gè)或多個(gè)IO事件發(fā)生后(狀態(tài)變?yōu)榭勺x),將控制權(quán)給應(yīng)用程序。服務(wù)器使用IO多路復(fù)用,借助select函數(shù)監(jiān)測(cè)IO事件的發(fā)生,當(dāng)監(jiān)聽(tīng)的文件描述符變?yōu)榭勺x時(shí)(IO事件發(fā)生),服務(wù)器就為相應(yīng)的狀態(tài)機(jī)執(zhí)行轉(zhuǎn)移,IO多路復(fù)用基本思就是復(fù)用單一或少量線程處理多種IO事件,并不是為了更快的處理IO,而是可以同時(shí)處理多個(gè)IO事件,IO事件可以是標(biāo)準(zhǔn)輸入輸出、socket等。
生活舉例:
好比你去銀行柜臺(tái),三個(gè)窗口只有一個(gè)服務(wù)員,你去辦理業(yè)務(wù)相當(dāng)于IO事件,辦理業(yè)務(wù)需要填表,這時(shí)相當(dāng)于你的事件還未準(zhǔn)備好,服務(wù)員可以接收其他窗口的客戶,這種場(chǎng)景相當(dāng)于服務(wù)員一個(gè)人(單一進(jìn)程)監(jiān)聽(tīng)了三個(gè)窗口的事件(IO事件),每次輪詢?cè)儐?wèn)每個(gè)窗口客戶是否填好了表格,沒(méi)好就去下一個(gè)窗口詢問(wèn),填好了就把這個(gè)表格交給后臺(tái)人員處理,當(dāng)然你可以每個(gè)窗口安排一個(gè)服務(wù)人員,并發(fā)進(jìn)行,但計(jì)算機(jī)不同的是,早期的cpu大多是單核cpu,cpu是分配時(shí)間片區(qū)做到并發(fā)的,相當(dāng)于一個(gè)人一天的工作時(shí)間是固定比如8小時(shí),2小時(shí)喝茶,2小時(shí)打王者等等,只不過(guò)這個(gè)人的手速很快,干起活來(lái)在你看來(lái)好像是瞬間同時(shí)完成的,這人就是cpu他就這么快!多核多cpu那是后來(lái)才有的事就另說(shuō)了。
實(shí)現(xiàn)系統(tǒng)IO多路復(fù)用的方式有哪些?這里不會(huì)介紹每個(gè)函數(shù)的具體用法,只讓讀者總體上對(duì)多路復(fù)用有一個(gè)概念模型,深入了解可以參考《深入理解計(jì)算機(jī)系統(tǒng)》等相關(guān)書籍
select函數(shù):系統(tǒng)中的IO被抽象為文件描述符,簡(jiǎn)單說(shuō)select函數(shù)輪詢這些傳給他的文件描述符,發(fā)現(xiàn)有事件就緒(數(shù)據(jù)從內(nèi)核空間到用戶空間卡拷貝完成),就將數(shù)據(jù)讀取出來(lái)返回給應(yīng)用程序。
poll函數(shù):和selec函數(shù)比較沒(méi)有本質(zhì)區(qū)別,但沒(méi)有最大文件描述數(shù)量的限制,文件描述符使用鏈表來(lái)存儲(chǔ),上限取決于系統(tǒng)最大文件句柄打開(kāi)數(shù),select上限是1024個(gè)文件描述符
epoll模式:select、poll采用輪詢的方式挨個(gè)檢測(cè)每個(gè)文件描述符是否就緒,如果描述符太多,每次都要循環(huán)查找效率太低,能不能使用回調(diào)的方式,當(dāng)文件描述符就緒時(shí)自動(dòng)回調(diào)通知,不用每次循環(huán),從而讓復(fù)雜度從O(n)降低到O(1) , 這也是epoll和前2個(gè)最大的區(qū)別,可以理解為event poll,他是一組函數(shù)而不是單個(gè)函數(shù)來(lái)實(shí)現(xiàn)多路復(fù)用的。
應(yīng)用場(chǎng)景是什么?IO多路復(fù)用最大應(yīng)用場(chǎng)景就是用以設(shè)計(jì)高并發(fā)的事件驅(qū)動(dòng)程序,redis是基于內(nèi)存的數(shù)據(jù)庫(kù),內(nèi)部使用IO多路復(fù)用處理客戶端高并發(fā)請(qǐng)求(連接、命令、回復(fù))
和redis一樣為了邏輯簡(jiǎn)潔、高并發(fā)、避免鎖的競(jìng)爭(zhēng)和上下文切換, mysql 線程池、nodejs也是基于IO多路復(fù)用理念來(lái)設(shè)計(jì)的事件驅(qū)動(dòng)程序。
IO多路復(fù)用的優(yōu)缺點(diǎn)?優(yōu)點(diǎn): IO多路復(fù)用可以用來(lái)設(shè)計(jì)并發(fā)事件驅(qū)動(dòng)程序,相比設(shè)計(jì)基于多進(jìn)程、多線程的并發(fā)服務(wù)器程序來(lái)說(shuō)是比較困難的,利用單線程處理多種IO事件使有限的資源利用最大化,避免多線程的上下文切換、鎖的競(jìng)爭(zhēng)。
缺點(diǎn):但這樣也帶來(lái)了編碼復(fù)雜,更大缺點(diǎn)是不能充分體現(xiàn)代多核處理器的優(yōu)勢(shì),更好做法是IO多路復(fù)用結(jié)合多線程的綜合設(shè)計(jì)理念來(lái)設(shè)計(jì)你的應(yīng)用程序。