關于同步,異步,阻塞,非阻塞,IOCP/epoll,select/poll,AIO ,NIO ,BIO的總結

轉載

IO基本概念

Linux環境

Linux的內核將所有外部設備都可以看做一個文件來操作。那么我們對與外部設備的操作都可以看做對文件進行操作。我們對一個文件的讀寫,都通過調用內核提供的系統調用;內核給我們返回一個file descriptor(fd,文件描述符)。對一個socket的讀寫也會有相應的描述符,稱為socketfd(socket描述符)。描述符就是一個數字(可以理解為一個索引),指向內核中一個結構體(文件路徑,數據區,等一些屬性)。應用程序對文件的讀寫就通過對描述符的讀寫完成。

一個基本的IO,它會涉及到兩個系統對象,一個是調用這個IO的進程對象,另一個就是系統內核(kernel)。當一個read操作發生時,它會經歷兩個階段:

通過read系統調用想內核發起讀請求。

內核向硬件發送讀指令,并等待讀就緒。

內核把將要讀取的數據復制到描述符所指向的內核緩存區中。

將數據從內核緩存區拷貝到用戶進程空間中。

同步,異步,阻塞,非阻塞

同步與異步

同步和異步關注的是消息通信機制 (synchronous communication/ asynchronous communication)

所謂同步,就是在發出一個調用時,在沒有得到結果之前,該調用就不返回。但是一旦調用返回,就得到返回值了。

換句話說,就是由調用者主動等待這個調用的結果。

而異步則是相反,調用在發出之后,這個調用就直接返回了,所以沒有返回結果。換句話說,當一個異步過程調用發出后,調用者不會立刻得到結果。而是在調用發出后,被調用者通過狀態、通知來通知調用者,或通過回調函數處理這個調用。

典型的異步編程模型比如Node.js

舉個通俗的例子:

你打電話問書店老板有沒有《分布式系統》這本書,如果是同步通信機制,書店老板會說,你稍等,”我查一下”,然后開始查啊查,等查好了(可能是5秒,也可能是一天)告訴你結果(返回結果)。

而異步通信機制,書店老板直接告訴你我查一下啊,查好了打電話給你,然后直接掛電話了(不返回結果)。然后查好了,他會主動打電話給你。在這里老板通過“回電”這種方式來回調。

實質:訪問數據的方式,同步需要當前線程讀寫數據,在讀寫數據的過程中還是會阻塞;異步只需要I/O操作完成的通知,當前進程并不主動讀寫數據,由操作系統內核完成數據的讀寫。

阻塞與非阻塞

阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態.

阻塞調用是指調用結果返回之前,當前線程會被掛起。調用線程只有在得到結果之后才會返回。

非阻塞調用指在不能立刻得到結果之前,該調用不會阻塞當前線程。

還是上面的例子,

你打電話問書店老板有沒有《分布式系統》這本書,你如果是阻塞式調用,你會一直把自己“掛起”,直到得到這本書有沒有的結果,如果是非阻塞式調用,你不管老板有沒有告訴你,你自己先一邊去玩了, 當然你也要偶爾過幾分鐘check一下老板有沒有返回結果。

在這里阻塞與非阻塞與是否同步異步無關。跟老板通過什么方式回答你結果無關。

IO模型(Reference Link

在linux系統下面,根據IO操作的是否被阻塞以及同步異步問題進行分類,可以得到下面五種IO模型

阻塞I/O模型

最常見的I/O模型是阻塞I/O模型,缺省情形下,所有文件操作都是阻塞的。我們以套接口為例來講解此模型。在進程空間中調用recvfrom,其系統調用直到數據報到達且被拷貝到應用進程的緩沖區中或者發生錯誤才返回,期間一直在等待。我們就說進程在從調用recvfrom開始到它返回的整段時間內是被阻塞的。

非阻塞I/O模型

進程把一個套接口設置成非阻塞是在通知內核:當所請求的I/O操作不能滿足要求時候,不把本進程投入睡眠,而是返回一個錯誤。也就是說當數據沒有到達時并不等待,而是以一個錯誤返回。

I/O復用模型

linux提供select/poll,進程通過將一個或多個fd傳遞給select或poll系統調用,阻塞在select;這樣select/poll可以幫我們偵測許多fd是否就緒。但是select/poll是順序掃描fd是否就緒,而且支持的fd數量有限。linux還提供了一個epoll系統調用,epoll是基于事件驅動方式,而不是順序掃描,當有fd就緒時,立即回調函數rollback;

信號驅動異步I/O模型

首先開啟套接口信號驅動I/O功能, 并通過系統調用sigaction安裝一個信號處理函數(此系統調用立即返回,進程繼續工作,它是非阻塞的)。當數據報準備好被讀時,就為該進程生成一個SIGIO信號。隨即可以在信號處理程序中調用recvfrom來讀數據報,井通知主循環數據已準備好被處理中。也可以通知主循環,讓它來讀數據報。

異步I/O模型

告知內核啟動某個操作,并讓內核在整個操作完成后(包括將數據從內核拷貝到用戶自己的緩沖區)通知我們。這種模型與信號驅動模型的主要區別是:信號驅動I/O:由內核通知我們何時可以啟動一個I/O操作;異步I/O模型:由內核通知我們I/O操作何時完成。

總結

前四種都是同步IO,在內核數據copy到用戶空間時都是阻塞的。

最后一種是異步IO,通過API把IO操作交由操作系統處理,當前進程不關心具體IO的實現,通過回調函數,或者信號量通知當前進程直接對IO返回結果進行處理。

首先一個IO操作其實分成了兩個步驟:發起IO請求和實際的IO操作,同步IO和異步IO的區別就在于第二個步驟是否阻塞,如果實際的IO讀寫阻塞請求進程,那么就是同步IO,因此阻塞IO、非阻塞IO、IO復用、信號驅動IO都是同步IO,如果不阻塞,而是操作系統幫你做完IO操作再將結果返回給你,那么就是異步IO。阻塞IO和非阻塞IO的區別在于第一步,發起IO請求是否會被阻塞,如果阻塞直到完成那么就是傳統的阻塞IO,如果不阻塞,那么就是非阻塞IO。

AIO,BIO,NIO

AIO異步非阻塞IO,AIO方式適用于連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與并發操作,編程比較復雜,JDK7開始支持。

NIO同步非阻塞IO,適用于連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,并發局限于應用中,編程比較復雜,JDK1.4開始支持。

BIO同步阻塞IO,適用于連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,并發局限于應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。

Java對BIO、NIO、AIO的支持:

JavaBIO : 同步并阻塞,服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。

JavaNIO : 同步非阻塞,服務器實現模式為一個請求一個線程,即客戶端發送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O請求時才啟動一個線程進行處理。(底層是epoll)

Java AIO(NIO.2) : 異步非阻塞,服務器實現模式為一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啟動線程進行處理。

AIO(Reference Link1ReferenceLink2

一般來說,服務器端的I/O主要有兩種情況:一是來自網絡的I/O;二是對文件(設備)的I/O。Windows的異步I/O模型能很好的適用于這兩種情況。而Linux針對前者提供了epoll模型,針對后者提供了AIO模型(關于是否把兩者統一起來爭論了很久)。

NIO(Reference Link

epoll,select/poll(Reference Link)

都是IO復用,I/O多路復用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。

epoll的效率更高,優化了select的輪詢操作,通過callback事件響應方式。

epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。

LT&&ET(epoll)

LT(level triggered)是缺省的工作方式,并且同時支持block和no-block socket.在這種做法中,內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表。

ET (edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變為就緒時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經就緒,并且不會再為那個文件描述符發送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再為就緒狀態了(比如,你在發送,接收或者接收請求,或者發送接收的數據少于一定量時導致了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once),不過在TCP協議中,ET模式的加速效用仍需要更多的benchmark確認。

ET和LT的區別就在這里體現,LT事件不會丟棄,而是只要讀buffer里面有數據可以讓用戶讀,則不斷的通知你。而ET則只在事件發生之時通知??梢院唵卫斫鉃長T是水平觸發,而ET則為邊緣觸發。LT模式只要有事件未處理就會觸發,而ET則只在高低電平變換時(即狀態從1到0或者0到1)觸發。[1]

select的幾大缺點:

(1)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大

(2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大

(3)select支持的文件描述符數量太小了,默認是1024

poll實現

poll的實現和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結構而不是select的fd_set結構.

epoll(reference Link

epoll既然是對select和poll的改進,就應該能避免上述的三個缺點。那epoll都是怎么解決的呢?在此之前,我們先看一下epoll和select和poll的調用接口上的不同,select和poll都只提供了一個函數——select或者poll函數。而epoll提供了三個函數,epoll_create,epoll_ctl和epoll_wait,epoll_create是創建一個epoll句柄;epoll_ctl是注冊要監聽的事件類型;epoll_wait則是等待事件的產生。

對于第一個缺點,epoll的解決方案在epoll_ctl函數中。每次注冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進內核,而不是在epoll_wait的時候重復拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。

對于第二個缺點,epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應的設備等待隊列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)并為每個fd指定一個回調函數,當設備就緒,喚醒等待隊列上的等待者時,就會調用這個回調函數,而這個回調函數會把就緒的fd加入一個就緒鏈表)。epoll_wait的工作實際上就是在這個就緒鏈表中查看有沒有就緒的fd(利用schedule_timeout()實現睡一會,判斷一會的效果,和select實現中的第7步是類似的)。

對于第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大于2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統內存關系很大。

使用mmap加速內核與用戶空間的消息傳遞。無論是select,poll還是epoll都需要內核把FD消息通知給用戶空間,如何避免不必要的內存拷貝就很重要,在這點上,epoll是通過內核于用戶空間mmap同一塊內存實現的。

總結

select,poll實現需要自己不斷輪詢所有fd集合,直到設備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調用epoll_wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,并喚醒在epoll_wait中進入睡眠的進程。雖然都要睡眠和交替,但是select和poll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的時候只要判斷一下就緒鏈表是否為空就行了,這節省了大量的CPU時間。這就是回調機制帶來的性能提升。

select,poll每次調用都要把fd集合從用戶態往內核態拷貝一次,并且要把current往設備等待隊列中掛一次,而epoll只要一次拷貝,而且把current往等待隊列上掛也只掛一次(在epoll_wait的開始,注意這里的等待隊列并不是設備等待隊列,只是一個epoll內部定義的等待隊列)。這也能節省不少的開銷。

IOCP (ReferenceLink|ConcreteRealization

I/O完成端口,是Windows環境下的異步IO處理模型。IOCP采用了線程池+隊列+重疊結構的內核機制完成任務。需要說明的是IOCP其實不僅可以接受套接字對象句柄,還可以接受文件對象句柄等。

IOCP本質是一種線程池的模型,當然這個線程池的核心工作就是去調用IO操作完成時的回調函數。是WINDOWS系統的一個內核對象。通過此對象,應用程序可以獲得異步IO的完成通知。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容