注:
1)本人非科班出身献联,文章的來源主要是基于一些能找到的資料,在理解的基礎(chǔ)上做一些總結(jié)歸納,以期對IO相關(guān)的知識體系化里逆。水平有限进胯,還請指教!
2)本文后半部分對IO所涉及的一些概念做了簡要概述运悲,如果讀者從頭開始閱讀的情況下感覺一頭霧水龄减,可以先去閱讀并熟悉一些基本的概念,有了基本的上下文概念后班眯,再從頭看起希停。
Linux的網(wǎng)絡(luò)IO模型
網(wǎng)絡(luò)IO的本質(zhì)是socket的讀寫,socket在Linux中被抽象為流署隘,IO可以理解為對流的操作宠能。
注意緩存IO這一概念
事先說明:
IO本身可以分為內(nèi)存IO、網(wǎng)絡(luò)IO和磁盤IO三種磁餐,一般討論IO時更多是指后兩者违崇,尤其是網(wǎng)絡(luò)IO。
-
阻塞/非阻塞:針對函數(shù)/方法的實現(xiàn)方式而言
即數(shù)據(jù)就緒之前是立刻返回還是等待诊霹,即發(fā)起IO請求后是否會阻塞羞延。
-
同步/異步
IO讀操作指數(shù)據(jù)流經(jīng):網(wǎng)絡(luò) -> 內(nèi)核緩沖區(qū) -> 用戶內(nèi)存
而同步和異步的主要區(qū)別在于數(shù)據(jù)從 內(nèi)核緩沖區(qū) -> 用戶內(nèi)存 這個過程需不需要用戶進(jìn)程等待。
?
對于一個網(wǎng)絡(luò)IO脾还,會涉及到兩個系統(tǒng)對象伴箩,一個是調(diào)用這個IO的process(or thread),另一個就是系統(tǒng)內(nèi)核(kernel)
當(dāng)一個read操作發(fā)生時鄙漏,它會經(jīng)歷兩個階段:
- 第一階段:等待數(shù)據(jù)準(zhǔn)備 (Waiting for the data to be ready)珍坊。
- 第二階段:將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 (Copying the data from the kernel to the process)瞎领。
對于socket流而言瓮增,
- 第一步:通常涉及等待網(wǎng)絡(luò)上的數(shù)據(jù)分組到達(dá)功咒,然后被復(fù)制到內(nèi)核的某個緩沖區(qū)。
- 第二步:把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用進(jìn)程緩沖區(qū)桦踊。
網(wǎng)絡(luò)應(yīng)用處理的是兩大類問題:網(wǎng)絡(luò)IO椅野、數(shù)據(jù)計算。前者給應(yīng)用帶來的性能瓶頸更大籍胯。
網(wǎng)絡(luò)IO的模型大致有如下幾種:
- 同步模型(synchronous IO)
- 阻塞IO模型(blocking IO)
- 非阻塞IO模型(non-blocking IO)
- 多路復(fù)用IO模型(multiplexing IO)
- 信號驅(qū)動IO模型(signal-driven IO)
- 異步IO(asynchronous IO)
Blocking IO
在Linux中鳄橘,默認(rèn)情況下所有的socket都是blocking,一個典型的讀操作流程如下:
[圖片上傳失敗...(image-9e85ea-1527599992824)]
當(dāng)用戶進(jìn)程調(diào)用了recvfrom這個系統(tǒng)調(diào)用芒炼,如上所述,會有兩個階段
- 準(zhǔn)備數(shù)據(jù)术徊。很多時候數(shù)據(jù)在一開始還沒有到達(dá)本刽,這個時候kernel就要等待足夠的數(shù)據(jù)到來。而用戶進(jìn)程會一直阻塞。
- 當(dāng)kernel等到數(shù)據(jù)準(zhǔn)備好了子寓,它會將數(shù)據(jù)從kernel中拷貝到用戶內(nèi)存暗挑,然后kernel返回,用戶進(jìn)程結(jié)束block狀態(tài)斜友,重新運行炸裆。
Blocking IO的特點就是IO執(zhí)行的兩個階段都是block了的。
Non-Blocking IO
在Linux中鲜屏,可以通過設(shè)置socket使其變?yōu)閚on-blocking烹看,其流程如下:
[圖片上傳失敗...(image-b07607-1527599992824)]
當(dāng)用戶進(jìn)程調(diào)用了recvfrom這個系統(tǒng)調(diào)用,如果kernel中的數(shù)據(jù)還沒有準(zhǔn)備好洛史,那么用戶進(jìn)程不會block而是立刻返回一個error惯殊,即從用戶的角度而言,不需要等待也殖,馬上得到一個結(jié)果土思。從圖中可以看出,用戶進(jìn)程在判斷結(jié)果是一個error后忆嗜,了解到數(shù)據(jù)還沒有準(zhǔn)備好己儒,于是就不斷重復(fù)上述操作直至kernel中的數(shù)據(jù)準(zhǔn)備好,然后它馬上將數(shù)據(jù)拷貝到了用戶內(nèi)存捆毫,然后返回闪湾。
Multiplexing IO
Select/Epoll,也被稱作是Event-Driven IO冻璃。好處是單個process可以同時處理多個網(wǎng)絡(luò)連接的IO响谓。
基本原理可見下面的“IO復(fù)用技術(shù)”。也叫多路IO就緒通知省艳。這是一種進(jìn)程預(yù)先告知內(nèi)核的能力娘纷,讓內(nèi)核發(fā)現(xiàn)進(jìn)程指定的一個或多個IO條件就緒了,就通知進(jìn)程跋炕。使得一個進(jìn)程能在一連串的事件上等待赖晶。
[圖片上傳失敗...(image-f4d1c4-1527599992824)]
這個流程和Blocking IO的流程其實并沒有太多不同,事實上僅從圖中看起來辐烂,由于需要進(jìn)行兩次系統(tǒng)調(diào)用遏插,可能更差一些。但是纠修,Select的優(yōu)勢在于它可以同時處理多個連接胳嘲。
如果處理的連接數(shù)不是很高的話,使用“Select/Epoll 的 Web Server”不一定比使用“多線程 + BIO的Web Server”性能更好扣草,反而延遲會更大了牛。
Select/Epoll的優(yōu)勢并不是對于單個連接能處理得更快颜屠,而是在于能處理更多的連接。
Asynchronous IO
[圖片上傳失敗...(image-2e105e-1527599992824)]
用戶進(jìn)程發(fā)起read操作之后鹰祸,立刻就可以開始去做其它的事甫窟。
而從kernel的角度,當(dāng)它受到一個asynchronous read之后蛙婴,首先它會立刻返回粗井,所以不會對用戶進(jìn)程產(chǎn)生任何block。然后街图,kernel會等待數(shù)據(jù)準(zhǔn)備完成浇衬,然后將數(shù)據(jù)拷貝到用戶內(nèi)存,當(dāng)這一切都完成之后台夺,kernel會給用戶進(jìn)程發(fā)送一個signal径玖,告訴它read操作完成了。
比較
[圖片上傳失敗...(image-89e729-1527599992824)]
IO復(fù)用技術(shù)
在IO編程過程中颤介,當(dāng)需要處理多個請求時梳星,可以使用 多線程 和 IO復(fù)用 的方式進(jìn)行處理。
IO復(fù)用是什么滚朵?
把多個IO的阻塞復(fù)用到一個select之類的阻塞上冤灾,從而使得系統(tǒng)在單線程的情況下同時支持處理多個請求。
IO復(fù)用常見的應(yīng)用場景:
- 服務(wù)器需要同時處理多個處于監(jiān)聽狀態(tài)和多個連接狀態(tài)的套接字辕近;
- 服務(wù)器需要處理多種網(wǎng)絡(luò)協(xié)議的套接字
IO復(fù)用的實現(xiàn)方式目前主要有select韵吨、poll和epoll。
select和poll的原理基本相同:
- 注冊待偵聽的fd(這里的fd創(chuàng)建時最好使用非阻塞)
- 每次調(diào)用都去檢查這些fd的狀態(tài)移宅,當(dāng)有一個或者多個fd就緒的時候返回
- 返回結(jié)果中包括已就緒和未就緒的fd
Linux網(wǎng)絡(luò)編程過程中归粉,相比于select/poll,epoll是有著更明顯優(yōu)勢的一種選擇漏峰。
-
支持一個進(jìn)程打開的socket描述符不受限制(僅受限于操作系統(tǒng)的最大文件句柄數(shù))糠悼。
Select的缺陷:一個進(jìn)程所打開的FD受限,默認(rèn)是2048浅乔;盡管數(shù)值可以更改倔喂,但同樣可能導(dǎo)致網(wǎng)絡(luò)效率下降;可以選擇多進(jìn)程的解決方案靖苇,但是進(jìn)程的創(chuàng)建本身代價不小席噩,而且進(jìn)程間數(shù)據(jù)同步遠(yuǎn)比不上線程間同步的高效。
epoll所支持的FD上限是最大可以打開文件的數(shù)目贤壁, /proc/sys/fs/file-max
-
IO效率可能隨著文件描述符數(shù)目的增加而線性下降悼枢。
select/poll是線性掃描FD的集合;epoll是根據(jù)FD上面的回調(diào)函數(shù)實現(xiàn)的脾拆,活躍的socket會主動去調(diào)用該回調(diào)函數(shù)萧芙,其它socket則不會给梅,相當(dāng)于市是一個AIO,只不過推動力在OS內(nèi)核双揪。
-
使用mmap加速內(nèi)核與用戶空間的消息傳遞。
zero-copy的一種包帚。
epoll的API更加簡單渔期。
IO復(fù)用還有一個 水平觸發(fā) 和 邊緣觸發(fā) 的概念:
- 水平觸發(fā):當(dāng)就緒的fd未被用戶進(jìn)程處理后,下一次查詢依舊會返回渴邦,這是select和poll的觸發(fā)方式疯趟。
- 邊緣觸發(fā):無論就緒的fd是否被處理,下一次不再返回谋梭。理論上性能更高信峻,但是實現(xiàn)相當(dāng)復(fù)雜,并且任何意外的丟失事件都會造成請求處理錯誤瓮床。epoll默認(rèn)使用水平觸發(fā)盹舞,通過相應(yīng)選項可以使用邊緣觸發(fā)。
Java的IO模型
- 傳統(tǒng)的BIO模型
- 偽異步
- NIO
- AIO
BIO
BIO是一個典型的網(wǎng)絡(luò)編程模型隘庄,是通常我們實現(xiàn)一個服務(wù)端程序的過程踢步。
步驟如下:
- 主線程accept請求阻塞
- 請求到達(dá),創(chuàng)建新的線程來處理這個套接字丑掺,完成對客戶端的響應(yīng)获印。
- 主線程繼續(xù)accept下一個請求
這種模型有一個很大的問題是:當(dāng)客戶端連接增多時,服務(wù)端創(chuàng)建的線程也會暴漲街州,系統(tǒng)性能會急劇下降兼丰。
偽異步
在BIO模型的基礎(chǔ)上,類似于 tomcat的bio connector唆缴,采用的是線程池來避免對于每一個客戶端都創(chuàng)建一個線程:把請求拋到線程池中異步等待處理鳍征。
NIO
NIO API主要是三個部分:緩沖區(qū)(Buffers)、通道(Channels)和 Selector琐谤。
NIO基于事件驅(qū)動思想來實現(xiàn)的蟆技,它采用Reactor模式實現(xiàn),主要用來解決BIO模型中一個服務(wù)端無法同時并發(fā)處理大量客戶端連接的問題斗忌。
NIO基于Selector進(jìn)行輪訓(xùn)质礼,當(dāng)socket有數(shù)據(jù)可讀、可寫织阳、連接完成眶蕉、新的TCP請求接入事件時,操作系統(tǒng)內(nèi)核會觸發(fā)Selector返回準(zhǔn)備就緒的SelectionKey集合唧躲,通過SelectableChannel進(jìn)行讀寫操作造挽。
由于jdk的Selector底層基于epoll實現(xiàn)碱璃,理論上可以同時處理操作系統(tǒng)最大文件句柄個數(shù)的連接。SelectableChannel的讀寫操作都是異步非阻塞的饭入,當(dāng)由于數(shù)據(jù)沒有就緒導(dǎo)致讀半包時嵌器,立即返回,不會同步阻塞等待數(shù)據(jù)就緒谐丢,當(dāng)TCP緩沖區(qū)數(shù)據(jù)就緒之后爽航,會觸發(fā)Selector的讀事件,驅(qū)動下一次讀操作乾忱。因此讥珍,一個Reactor線程就可以同時處理N歌客戶端的連接,使得Java服務(wù)器的并發(fā)讀寫能力得到極大的提升窄瘟。
JDK1.4開始引入了NIO類庫衷佃,這里的NIO指的是Non-block IO,主要是使用Selector多路復(fù)用器來實現(xiàn)蹄葱。Selector在Linux等主流操作系統(tǒng)上是通過epoll實現(xiàn)的氏义。
NIO的實現(xiàn)流程,類似于select:
- 創(chuàng)建ServerSocketChannel監(jiān)聽客戶端連接并綁定監(jiān)聽端口新蟆,設(shè)置為非阻塞模式觅赊。
- 創(chuàng)建Reactor線程,創(chuàng)建多路復(fù)用器(Selector)并啟動線程琼稻。
- 將ServerSocketChannel注冊到Reactor線程的Selector上吮螺。監(jiān)聽accept事件。
- Selector在線程run方法中無線循環(huán)輪詢準(zhǔn)備就緒的Key帕翻。
- Selector監(jiān)聽到新的客戶端接入鸠补,處理新的請求,完成tcp三次握手嘀掸,建立物理連接紫岩。
- 將新的客戶端連接注冊到Selector上,監(jiān)聽讀操作睬塌。讀取客戶端發(fā)送的網(wǎng)絡(luò)消息泉蝌。
- 客戶端發(fā)送的數(shù)據(jù)就緒則讀取客戶端請求,進(jìn)行處理揩晴。
相比BIO勋陪,NIO的編程非常復(fù)雜。
AIO
JDK1.7引入NIO2.0硫兰,提供了異步文件通道和異步套接字通道的實現(xiàn)诅愚。其底層在windows上是通過IOCP,在Linux上是通過epoll來實現(xiàn)的(LinuxAsynchronousChannelProvider.java,UnixAsynchronousServerSocketChannelImpl.java)劫映。
- 創(chuàng)建AsynchronousServerSocketChannel违孝,綁定監(jiān)聽端口
- 調(diào)用AsynchronousServerSocketChannel的accpet方法刹前,傳入自己實現(xiàn)的CompletionHandler。包括上一步雌桑,都是非阻塞的
- 連接傳入喇喉,回調(diào)CompletionHandler的completed方法,在里面筹燕,調(diào)用AsynchronousSocketChannel的read方法轧飞,傳入負(fù)責(zé)處理數(shù)據(jù)的CompletionHandler。
- 數(shù)據(jù)就緒撒踪,觸發(fā)負(fù)責(zé)處理數(shù)據(jù)的CompletionHandler的completed方法。繼續(xù)做下一步處理即可大渤。
- 寫入操作類似制妄,也需要傳入CompletionHandler。
其編程模型相比NIO有了不少的簡化泵三。
. | 同步阻塞IO | 偽異步IO | NIO | AIO |
---|---|---|---|---|
客戶端數(shù)目 :IO線程 | 1 : 1 | m : n | m : 1 | m : 0 |
IO模型 | 同步阻塞IO | 同步阻塞IO | 同步非阻塞IO | 異步非阻塞IO |
吞吐量 | 低 | 中 | 高 | 高 |
編程復(fù)雜度 | 簡單 | 簡單 | 非常復(fù)雜 | 復(fù)雜 |
概念
同步異步/阻塞非阻塞 角度一
同步和異步
這兩個概念與消息的通知機制 (synchronous communication/ asynchronous communication)有關(guān)耕捞。
[圖片上傳失敗...(image-8a971a-1527599992824)]
同步:
?
一個任務(wù)的完成需要依賴另外一個任務(wù)時,只有等待被依賴的任務(wù)完成后烫幕,依賴的任務(wù)才能算完成俺抽,這是一種可靠的任務(wù)序列。要么都成功较曼,要么都失敗磷斧,兩個任務(wù)的狀態(tài)可以保持一致。? 在發(fā)出一個調(diào)用時捷犹,在沒有得到結(jié)果之前弛饭,該調(diào)用就不返回。但是一旦調(diào)用返回萍歉,就得到返回值了侣颂。換句話說,就是由調(diào)用者主動等待這個調(diào)用的結(jié)果枪孩。
? 對于同步型的調(diào)用憔晒,應(yīng)用層需要自己去向系統(tǒng)內(nèi)核問詢,如果數(shù)據(jù)還未讀取完畢蔑舞,那此時讀取文件的任務(wù)還未完成拒担,應(yīng)用層根據(jù)其阻塞和非阻塞的劃分,或掛起或去做其他事情(所以同步和異步并不決定其等待數(shù)據(jù)返回時的狀態(tài))斗幼;如果數(shù)據(jù)已經(jīng)讀取完畢澎蛛,那此時系統(tǒng)內(nèi)核將數(shù)據(jù)返回給應(yīng)用層,應(yīng)用層即可以用取得的數(shù)據(jù)做其他相關(guān)的事情蜕窿。
異步:
?
不需要等到被依賴的任務(wù)完成谋逻,只是通知被依賴的任務(wù)要完成什么工作呆馁,依賴的任務(wù)也立即執(zhí)行,只要自己完成了整個任務(wù)就算完成了毁兆。至于被依賴的任務(wù)最終是否真正完成浙滤,依賴它的任務(wù)無法確定,所以它是不可靠的任務(wù)序列气堕。? 調(diào)用在發(fā)出之后纺腊,這個調(diào)用就直接返回了,所以沒有返回結(jié)果茎芭。換句話說揖膜,當(dāng)一個異步過程調(diào)用發(fā)出后,調(diào)用者不會立刻得到結(jié)果梅桩。而是在調(diào)用發(fā)出后壹粟,被調(diào)用者*通過狀態(tài)、通知來通知調(diào)用者宿百,或通過回調(diào)函數(shù)處理這個調(diào)用趁仙。
? 而對于異步型的調(diào)用,應(yīng)用層無需主動向系統(tǒng)內(nèi)核問詢垦页,在系統(tǒng)內(nèi)核讀取完文件數(shù)據(jù)之后雀费,會主動通知應(yīng)用層數(shù)據(jù)已經(jīng)讀取完畢,此時應(yīng)用層即可以接收系統(tǒng)內(nèi)核返回過來的數(shù)據(jù)痊焊,再做其他事情盏袄。
也就是說,是否是同步還是異步宋光,關(guān)注的是任務(wù)完成時消息通知的方式貌矿。由調(diào)用方盲目主動問詢的方式是同步調(diào)用,由被調(diào)用方主動通知調(diào)用方任務(wù)已完成的方式是異步調(diào)用罪佳。
消息的三種通知機制:狀態(tài)逛漫、通知和回調(diào)。
前者低效赘艳,后兩者高效酌毡、類似。
阻塞與非阻塞
這兩個概念與程序(線程)等待消息通知(無所謂同步或者異步)時的狀態(tài)有關(guān)蕾管。
[圖片上傳失敗...(image-5c552e-1527599992824)]
阻塞調(diào)用:
? 是指調(diào)用結(jié)果返回之前枷踏,當(dāng)前線程會被掛起,一直處于等待消息通知掰曾,不能夠執(zhí)行其他業(yè)務(wù)旭蠕。函數(shù)只有在得到結(jié)果之后才會返回。
非阻塞調(diào)用:
? 和阻塞的概念相對應(yīng),指在不能立刻得到結(jié)果之前掏熬,該函數(shù)不會阻塞當(dāng)前線程佑稠,而會立刻返回去完成其他任務(wù)。
總結(jié)來說旗芬,是否是阻塞還是非阻塞舌胶,關(guān)注的是接口調(diào)用(發(fā)出請求)后等待數(shù)據(jù)返回時的狀態(tài)。被掛起無法執(zhí)行其他操作的則是阻塞型的疮丛,可以被立即「抽離」去完成其他「任務(wù)」的則是非阻塞型的幔嫂。
阻塞和同步的討論
有人也許會把阻塞調(diào)用和同步調(diào)用等同起來,實際上它們是不同的誊薄。**
1履恩、對于同步調(diào)用來說,很多時候當(dāng)前線程可能還是激活的呢蔫,只是從邏輯上當(dāng)前函數(shù)沒有返回而已似袁,此時,這個線程可能也會處理其他的消息咐刨。還有一點,在這里先擴展下:
(a) 如果這個線程在等待當(dāng)前函數(shù)返回時扬霜,仍在執(zhí)行其他消息處理定鸟,那這種情況就叫做同步非阻塞;
(b) 如果這個線程在等待當(dāng)前函數(shù)返回時著瓶,沒有執(zhí)行其他消息處理联予,而是處于掛起等待狀態(tài),那這種情況就叫做同步阻塞材原;
所以同步的實現(xiàn)方式會有兩種:同步阻塞沸久、同步非阻塞;同理余蟹,異步也會有兩種實現(xiàn):異步阻塞卷胯、異步非阻塞;
2威酒、對于阻塞調(diào)用來說窑睁,則當(dāng)前線程就會被掛起等待當(dāng)前函數(shù)返回;
雖然表面上看非阻塞的方式可以明顯的提高CPU的利用率葵孤,但是也帶了另外一種后果就是系統(tǒng)的線程切換增加担钮。增加的CPU執(zhí)行時間能不能補償系統(tǒng)的切換成本需要好好評估。
同步異步/阻塞非阻塞 角度二
[圖片上傳失敗...(image-3a670c-1527599992824)]
在說明synchronous IO和asynchronous IO的區(qū)別之前尤仍,需要先給出兩者的定義箫津。Stevens給出的定義(其實是POSIX的定義)是這樣子的:
? **A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes; **
An asynchronous I/O operation does not cause the requesting process to be blocked;
兩者的區(qū)別就在于synchronous IO做”IO operation”的時候會將process阻塞。按照這個定義,之前所述的blocking IO苏遥,non-blocking IO饼拍,IO multiplexing都屬于synchronous IO。
有人可能會說暖眼,non-blocking IO并沒有被block啊惕耕。這里有個非常“狡猾”的地方诫肠,定義中所指的”IO operation”是指真實的IO操作司澎,就是例子中的recvfrom這個system call。non-blocking IO在執(zhí)行recvfrom這個system call的時候栋豫,如果kernel的數(shù)據(jù)沒有準(zhǔn)備好挤安,這時候不會block進(jìn)程。但是丧鸯,當(dāng)kernel中數(shù)據(jù)準(zhǔn)備好的時候蛤铜,recvfrom會將數(shù)據(jù)從kernel拷貝到用戶內(nèi)存中,這個時候進(jìn)程是被block了丛肢,在這段時間內(nèi)围肥,進(jìn)程是被block的。而asynchronous IO則不一樣蜂怎,當(dāng)進(jìn)程發(fā)起IO 操作之后穆刻,就直接返回再也不理睬了,直到kernel發(fā)送一個信號杠步,告訴進(jìn)程說IO完成氢伟。在這整個過程中,進(jìn)程完全沒有被block幽歼。
同步異步/阻塞非阻塞 角度三
"同步異步"和"阻塞非阻塞"是兩個不同范圍的概念朵锣。
synchronous / asynchronous is to describe the relation between two modules.
blocking / non-blocking is to describe the situation of one module.
An example:
"I": a
"bookstore" : b
a asks b: do you have a book named "c++ primer"?
blocking: before b answers a, a keeps waiting there for the answer. Now a (one module) is blocking. a and b are two threads or two processes or one thread or one process? we DON'T know.
non-blocking: before b answers a, a just leaves there and every two minutes, a comes here for looking for the answer. Here a (one module) is non-blocking. a and b are two threads or two processes or one process? we DON'T know. BUT we are sure that a and b couldn't be one thread.
synchronous: before b answers a, a keeps waiting there for the answer. It means that a can't continue until b finishes its job. Now we say: a and b (two modules) is synchronous. a and b are two threads or two processes or one thread or one process? we DON'T know.
asynchronous: before b answers a, a leaves there and a can do other jobs. When b gets the answer, b will call a: hey! I have it! Then a will come to b to get the book when a is free. Now we say: a and b (two modules) is asynchronous. a and b are two threads or two processes or one process? we DON'T know. BUT we are sure that a and b couldn't be one thread.
同步異步/阻塞非阻塞 角度四
同步和異步是一個非常廣的概念,它們的重點在于多個任務(wù)和事件發(fā)生時甸私,一個事件的發(fā)生或執(zhí)行是否會導(dǎo)致整個流程的暫時等待诚些。
阻塞和非阻塞的區(qū)別關(guān)鍵在于當(dāng)發(fā)出請求一個操作時,如果條件不滿足颠蕴,是會一直等待還是返回一個標(biāo)志信息泣刹。
在討論IO(硬盤、網(wǎng)絡(luò)犀被、外設(shè))時椅您,一個完整的IO讀請求操作包括兩個階段:
1)查看數(shù)據(jù)是否就緒;
2)進(jìn)行數(shù)據(jù)拷貝(內(nèi)核將數(shù)據(jù)拷貝到用戶線程)
阻塞(blocking IO)和非阻塞(non-blocking IO)的區(qū)別就在于第一個階段寡键,如果數(shù)據(jù)沒有就緒掀泳,在查看數(shù)據(jù)是否就緒的過程中是一直等待雪隧,還是直接返回一個標(biāo)志信息。
在《Unix網(wǎng)絡(luò)編程》一書中對同步IO和異步IO的定義是這樣的:
A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes.
An asynchronous I/O operation does not cause the requesting process to be blocked.
事實上员舵,同步IO和異步IO模型是針對用戶線程和內(nèi)核的交互來說的:
同步IO:當(dāng)用戶發(fā)出IO請求操作之后脑沿,如果數(shù)據(jù)沒有就緒,需要通過用戶線程或者內(nèi)核不斷地去輪詢數(shù)據(jù)是否就緒马僻,當(dāng)數(shù)據(jù)就緒時庄拇,再將數(shù)據(jù)從內(nèi)核拷貝到用戶線程;
異步IO:只有IO請求操作的發(fā)出是由用戶線程來進(jìn)行的韭邓,IO操作的兩個階段都是由內(nèi)核自動完成措近,然后發(fā)送通知告知用戶線程IO操作已經(jīng)完成。也就是說在異步IO中女淑,不會對用戶線程產(chǎn)生任何阻塞瞭郑。
這是同步IO和異步IO關(guān)鍵區(qū)別所在,同步IO和異步IO的關(guān)鍵區(qū)別反映在數(shù)據(jù)拷貝階段是由用戶線程完成還是內(nèi)核完成鸭你。所以說<u>異步IO必須要有操作系統(tǒng)的底層支持</u>屈张。
注意同步IO和異步IO與阻塞IO和非阻塞IO是不同的兩組概念。
阻塞IO和非阻塞IO是反映在當(dāng)用戶請求IO操作時袱巨,如果數(shù)據(jù)沒有就緒阁谆,是用戶線程一直等待數(shù)據(jù)就緒,還是會收到一個標(biāo)志信息這一點上面的愉老。也就是說笛厦,阻塞IO和非阻塞IO是反映在IO操作的第一個階段,在查看數(shù)據(jù)是否就緒時是如何處理的俺夕。
同步/異步 與 阻塞/非阻塞
同步阻塞
效率是最低的,
異步阻塞
異步操作是可以被阻塞住的贱鄙,只不過它不是在處理消息時阻塞劝贸,而是在等待消息通知時被阻塞。
同步非阻塞
實際上是效率低下的逗宁,
這個程序<u>需要在兩種不同的行為之間來回的切換</u>映九,效率可想而知是低下的。
異步非阻塞
效率更高瞎颗。
用戶空間與內(nèi)核空間
現(xiàn)在操作系統(tǒng)都是采用虛擬存儲器件甥,對32位操作系統(tǒng)而言,它的尋址空間(虛擬存儲空間)為4G(2的32次方)哼拔。
操作系統(tǒng)的核心是內(nèi)核引有,獨立于普通的應(yīng)用程序,可以訪問受保護(hù)的內(nèi)存空間倦逐,也有訪問底層硬件設(shè)備的所有權(quán)限譬正。
為了保證用戶進(jìn)程不能直接操作內(nèi)核(kernel),保證內(nèi)核的安全,操作系統(tǒng)將虛擬空間劃分為兩部分曾我,<u>一部分為內(nèi)核空間粉怕,一部分為用戶空間</u>。
針對linux操作系統(tǒng)而言抒巢,將最高的1G字節(jié)(從虛擬地址0xC0000000到0xFFFFFFFF)贫贝,供內(nèi)核使用,稱為內(nèi)核空間蛉谜,而將較低的3G字節(jié)(從虛擬地址0x00000000到0xBFFFFFFF)稚晚,供各個進(jìn)程使用,稱為用戶空間悦陋。
進(jìn)程切換
為了控制進(jìn)程的執(zhí)行蜈彼,內(nèi)核必須有能力掛起正在CPU上運行的進(jìn)程,并恢復(fù)以前掛起的某個進(jìn)程的執(zhí)行俺驶。這種行為被稱為進(jìn)程切換/任務(wù)切換/上下文切換幸逆。因此可以說,任何進(jìn)程都是在操作系統(tǒng)內(nèi)核的支持下運行的暮现,是與內(nèi)核緊密相關(guān)的还绘。
從一個進(jìn)程的運行轉(zhuǎn)到另一個進(jìn)程上運行,這個過程中經(jīng)過下面這些變化:
- 保存處理機上下文栖袋,包括程序計數(shù)器和其他寄存器医窿。
- 更新PCB信息吮龄。
- 把進(jìn)程的PCB移入相應(yīng)的隊列,如就緒、在某事件阻塞等隊列瓶堕。
- 選擇另一個進(jìn)程執(zhí)行,并更新其PCB蒙揣。
- 更新內(nèi)存管理的數(shù)據(jù)結(jié)構(gòu)锻全。
- 恢復(fù)處理機上下文。
進(jìn)程的阻塞
正在執(zhí)行的進(jìn)程匾乓,由于期待的某些事件未發(fā)生捞稿,如請求系統(tǒng)資源失敗、等待某種操作的完成拼缝、新數(shù)據(jù)尚未到達(dá)或無新工作做等娱局,則由系統(tǒng)自動執(zhí)行阻塞原語(Block),使自己由運行狀態(tài)變?yōu)樽枞麪顟B(tài)咧七∷テ耄可見,進(jìn)程的阻塞是進(jìn)程自身的一種主動行為继阻,也因此只有處于運行態(tài)的進(jìn)程(獲得CPU)娇斩,才可能將其轉(zhuǎn)為阻塞狀態(tài)仁卷。當(dāng)進(jìn)程進(jìn)入阻塞狀態(tài),是不占用CPU資源的
犬第。
文件描述符fd
文件描述符(File descriptor)是計算機科學(xué)中的一個術(shù)語锦积,是一個用于表述指向文件的引用的抽象化概念
。
文件描述符在形式上是一個非負(fù)整數(shù)歉嗓。實際上丰介,它是一個索引值,指向內(nèi)核為每一個進(jìn)程所維護(hù)的該進(jìn)程打開文件的記錄表
鉴分。當(dāng)程序打開一個現(xiàn)有文件或者創(chuàng)建一個新文件時哮幢,內(nèi)核向進(jìn)程返回一個文件描述符。在程序設(shè)計中志珍,一些涉及底層的程序編寫往往會圍繞著文件描述符展開橙垢。但是文件描述符這一概念往往只適用于UNIX、Linux這樣的操作系統(tǒng)伦糯。
緩存 IO
緩存 IO 又被稱作標(biāo)準(zhǔn) IO柜某,大多數(shù)文件系統(tǒng)的默認(rèn) IO 操作都是緩存 IO。在 Linux 的緩存 IO 機制中敛纲,操作系統(tǒng)會將 IO 的數(shù)據(jù)緩存在文件系統(tǒng)的頁緩存( page cache )中喂击,也就是說,數(shù)據(jù)會先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中淤翔,然后才會從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間翰绊。
緩存 IO 的缺點:
數(shù)據(jù)在傳輸過程中需要在應(yīng)用程序地址空間和內(nèi)核進(jìn)行多次數(shù)據(jù)拷貝操作,這些數(shù)據(jù)拷貝操作所帶來的 CPU 以及內(nèi)存開銷是非常大的旁壮。