題目看上去真的很亂,寫之前先貼一張寫之前理出來的思路圖:
題目里那些亂七八糟的名詞都能在圖里找到自己的位置了沛励。 下面就是解釋一下這張圖了墩崩。
從IO開始講吧,先簡單解釋一下IO侯勉。其實(shí)IO這個(gè)概念真的很雜容易混淆鹦筹,我理解的IO是分兩個(gè)大概念的,即網(wǎng)絡(luò)IO和磁盤IO址貌。網(wǎng)絡(luò)IO就是socket網(wǎng)絡(luò)數(shù)據(jù)傳輸铐拐,磁盤IO就是磁盤文件讀寫這些。上圖中的IO理論思想在網(wǎng)絡(luò)IO和磁盤IO中都可以適用的练对,因?yàn)檫@兩個(gè)有很多共同之處遍蟋。
為什么有共同之處呢?可以這么理解螟凭。磁盤IO就是文件讀寫嘛虚青,文件是在磁盤上的要讀到內(nèi)存中,必然會(huì)涉及用戶態(tài)和內(nèi)核態(tài)螺男,這里就不解釋用戶態(tài)和內(nèi)核態(tài)了棒厘,理解起來就是用戶態(tài)問內(nèi)核態(tài)要數(shù)據(jù),那用戶態(tài)就是調(diào)用方下隧,內(nèi)核態(tài)就是被調(diào)用方奢人。而網(wǎng)絡(luò)IO也很相似啊,網(wǎng)絡(luò)IO最簡單的理解就是一個(gè)接口被調(diào)用了淆院,總之會(huì)有一個(gè)socket連接過來何乎,讓被調(diào)用的服務(wù)去干嘛干嘛,就可以理解為這個(gè)socket連接是調(diào)用方,被調(diào)用的服務(wù)當(dāng)然就是被調(diào)用方了支救。畫了一張圖總結(jié)一下:
我們所說的IO通常意義上是指磁盤IO的輸入/輸出流抢野,即I/O流。其實(shí)在磁盤IO體系中不止有流式部分各墨,還有非流式部分蒙保,貼兩張網(wǎng)上的圖:
所以,這么一頓分析下來欲主,一般我們打交道比較多的就是磁盤IO流式部分了,而這么長的名字平時(shí)我們只說IO逝嚎,有點(diǎn)搞混的扁瓢。當(dāng)然網(wǎng)絡(luò)IO和磁盤IO的區(qū)分界限也沒有那么清楚,比如字節(jié)流字符流用于文件讀寫沒有問題补君,但是網(wǎng)絡(luò)IO中也避免不了使用字節(jié)流啊引几,這就看個(gè)人理解了。
上面是理了一下IO挽铁,接下來解釋一下同步/異步和阻塞/非阻塞伟桅。
還是看上面調(diào)用方和被調(diào)用方那張圖,記住關(guān)鍵的一點(diǎn)叽掘,同步/異步是描述調(diào)用方是否等待調(diào)用結(jié)果返回的楣铁,阻塞/非阻塞是描述被調(diào)用方線程狀態(tài)的。
拿磁盤IO舉例:
同步阻塞IO就是用戶態(tài)的線程發(fā)起read()/write()調(diào)用后更扁,會(huì)一直等待調(diào)用結(jié)果返回盖腕,而在內(nèi)核態(tài),要去讀一個(gè)磁盤文件浓镜,數(shù)據(jù)不一定立馬就能準(zhǔn)備好啊溃列,同步阻塞IO就是內(nèi)核態(tài)的線程會(huì)一直能數(shù)據(jù)準(zhǔn)備好再去讀。盜張圖貼一下:
同步非阻塞IO同理用戶態(tài)發(fā)起read()/write()調(diào)用后膛薛,也會(huì)一直等待調(diào)用結(jié)果返回听隐,但是在內(nèi)核態(tài),如果數(shù)據(jù)沒有準(zhǔn)備好哄啄,內(nèi)核態(tài)的線程可不會(huì)等著雅任,就直接干別的去了,那就需要用戶態(tài)的線程一直去詢問數(shù)據(jù)有沒有處理好啊咨跌,一直問一直問這個(gè)就叫輪詢椿访。再貼一張圖:
那什么是IO多路復(fù)用呢?可以看出同步非阻塞IO是需要用戶態(tài)要有一個(gè)線程不停地去輪詢的虑润,這就很消耗CPU資源啊成玫,用戶態(tài)就很不樂意了,就在想能不能這個(gè)輪詢的讓內(nèi)核態(tài)自己去做就好了,這就是IO多路復(fù)用了哭当。所以IO多路復(fù)用是在同步非阻塞IO基礎(chǔ)上的一次演進(jìn)猪腕,即IO多路復(fù)用也必然是同步非阻塞IO。這個(gè)輪詢的活是就是select钦勘、poll和或者epoll這三個(gè)機(jī)制來完成的陋葡,也就是select、poll和或者epoll這三個(gè)是內(nèi)核態(tài)的系統(tǒng)調(diào)用彻采,三種不同的方式去找到準(zhǔn)備好的數(shù)據(jù)腐缤。需要注意的是select和poll是輪詢的方式去找到準(zhǔn)備好的數(shù)據(jù),epoll已經(jīng)不用輪詢了肛响,這個(gè)后面會(huì)說岭粤。既然都是在內(nèi)核態(tài)找到準(zhǔn)備好的數(shù)據(jù)了,更接近OS底層的調(diào)用性能也必然是比在用戶態(tài)的時(shí)候好特笋,既然這樣剃浇,那何必只去找用戶態(tài)某一個(gè)線程需要的數(shù)據(jù)呢,那就替用戶態(tài)所有干等著的線程找準(zhǔn)備好的數(shù)據(jù)吧猎物,這就是多路復(fù)用這個(gè)詞的來源虎囚。
上面是在磁盤IO理解了同步阻塞IO、同步非阻塞IO和多路復(fù)用IO蔫磨,那么在網(wǎng)絡(luò)IO怎么理解呢淘讥?
對于網(wǎng)絡(luò)IO,同步阻塞IO不用說了堤如,一個(gè)socket連接過來了适揉,里面的數(shù)據(jù)并不一定準(zhǔn)備好了,如果被調(diào)用服務(wù)的線程在干等著socket中的數(shù)據(jù)準(zhǔn)備好煤惩,那就是同步阻塞IO嫉嘀。如果這個(gè)服務(wù)線程不等呢?而是去詢問其他socket連接中的數(shù)據(jù)有沒有準(zhǔn)備好魄揉,那這就是同步非阻塞IO剪侮。
網(wǎng)絡(luò)IO和磁盤IO的同步非阻塞IO的理解還是有點(diǎn)區(qū)別的,磁盤IO的同步非阻塞的輪詢操作是由調(diào)用方去做的洛退,而且輪詢的是被調(diào)用方瓣俯,但是網(wǎng)絡(luò)IO的同步非阻塞總不能讓調(diào)用方去輪詢吧,難不成還一遍一遍的socket連接過來兵怯?所以只能被調(diào)用方去輪詢彩匕,而且輪詢的是調(diào)用方即一組socket連接,所以網(wǎng)絡(luò)IO的同步非阻塞IO也可以成為是IO多路復(fù)用媒区,即是被調(diào)用方在輪詢又有多路復(fù)用這個(gè)概念在里面啊驼仪。
JAVA NIO是NIO思想在JAVA領(lǐng)域的實(shí)現(xiàn)掸犬,所以很多人說JAVA NIO是多路復(fù)用IO也沒什么問題。既然是NIO思想在JAVA領(lǐng)域的實(shí)現(xiàn)绪爸,必然在網(wǎng)絡(luò)IO和磁盤IO都是可用的湾碎。在網(wǎng)絡(luò)IO應(yīng)用的關(guān)鍵詞就是Selector,即一組socket連接注冊到上面奠货。在磁盤IO的應(yīng)用關(guān)鍵字就是FileChannel文件通道和Buffer緩沖區(qū)介褥,也是用了select、poll或者epoll這一套递惋。
理清了上面這些概念柔滔,然后就可以了解一下select、poll和epoll萍虽。
1.select
select函數(shù)就是上面說的睛廊,做了遍歷輪詢的活,不止替一個(gè)用戶態(tài)線程找準(zhǔn)備好的數(shù)據(jù)贩挣,而是把很多個(gè)文件描述符fd放進(jìn)一個(gè)set集合中遍歷,就是替多個(gè)用戶態(tài)線程找準(zhǔn)備好的數(shù)據(jù)没酣。當(dāng)然王财,既然是set集合那就有數(shù)量上限,32位機(jī)器上默認(rèn)是1024個(gè)裕便,64位機(jī)器上默認(rèn)是2048個(gè)绒净。貼一張圖:
時(shí)間復(fù)雜度:O(n)
select的缺點(diǎn):
(1)單進(jìn)程可以打開fd有限制;
(2)對socket進(jìn)行掃描時(shí)是線性掃描偿衰,即采用輪詢的方法挂疆,效率較低;
(3)用戶空間和內(nèi)核空間的復(fù)制非常消耗資源下翎;
2.poll
其實(shí)poll調(diào)用過程和select一樣缤言,只不過采用鏈表的方式替換select的set集合去存儲(chǔ)fd,這樣連接數(shù)就沒有限制了视事。時(shí)間復(fù)雜度同樣為O(n)胆萧。
3.epoll
應(yīng)用示例:Nginx。
epoll就不像前面兩個(gè)把fd放進(jìn)一個(gè)集合里去遍歷了俐东,而是采用注冊回調(diào)函數(shù)跌穗,在文件描述就緒的時(shí)候網(wǎng)卡驅(qū)動(dòng)會(huì)去觸發(fā)這個(gè)回調(diào)函數(shù),通知說這個(gè)fd的數(shù)據(jù)已經(jīng)準(zhǔn)備好了虏辫,這就很nice了蚌吸。貼一下圖:
這樣就既沒有連接數(shù)限制,又不用去遍歷輪詢消耗CPU砌庄。而且時(shí)間復(fù)雜度為O(1)羹唠。?
epoll有兩種工作方式:1.水平觸發(fā)(LT)2.邊緣觸發(fā)(ET)?
LT模式:若就緒的事件一次沒有處理完要做的事件奕枢,就會(huì)一直去處理。即就會(huì)將沒有處理完的事件繼續(xù)放回到就緒隊(duì)列之中(即那個(gè)內(nèi)核中的鏈表)肉迫,一直進(jìn)行處理验辞。?
ET模式:就緒的事件只能處理一次,若沒有處理完會(huì)在下次的其它事件就緒時(shí)再進(jìn)行處理喊衫。而若以后再也沒有就緒的事件跌造,那么剩余的那部分?jǐn)?shù)據(jù)也會(huì)隨之而丟失。?
由此可見:ET模式的效率比LT模式的效率要高很多族购。只是如果使用ET模式壳贪,就要保證每次進(jìn)行數(shù)據(jù)處理時(shí),要將其處理完寝杖,不能造成數(shù)據(jù)丟失违施,這樣對編寫代碼的人要求就比較高。?
需要注意的是瑟幕,ET模式只支持非阻塞的讀寫:為了保證數(shù)據(jù)的完整性磕蒲。
到這里IO的同步模型就梳理得差不多了,還有一個(gè)異步模型只盹。
按剛剛的理解辣往,異步模型是需要調(diào)用方發(fā)起調(diào)用動(dòng)作后就不等了,去干別的事殖卑。而我們平常的編程站削,代碼是一行一行寫下來,運(yùn)行的時(shí)候也是一個(gè)線程一行一行的執(zhí)行下來孵稽,這行的結(jié)果沒出來呢線程也不會(huì)去干別的事许起,所以我們?nèi)粘5木幊潭际峭骄幊蹋亲霾坏秸嬲漠惒絀O的菩鲜。顧名思義異步IO需要特殊的異步編程語法园细,現(xiàn)在有的就是協(xié)程,這已經(jīng)涉及我的知識盲區(qū)了接校,就不繼續(xù)寫了珊肃。