Java IO 阻塞 非阻塞式IO 同步 非同步IO

2021-08-19 23:33:28 字數 4882 閱讀 6998

同步(synchronous) io和非同步(asynchronous) io,阻塞(blocking) io和非阻塞(non-blocking)io分別是什麼,到底有什麼區別?這個問題其實不同的人給出的答案都可能不同,比如wiki,就認為asynchronous io和non-blocking io是乙個東西。這其實是因為不同的人的知識背景不同,並且在討論這個問題的時候上下文(context)也不相同。所以,為了更好的回答這個問題,我先限定一下本文的上下文。本文討論的背景是linux環境下的network io。

本文最重要的參考文獻是richardstevens的「unix® network programmingvolume 1, third edition: the sockets networking

」,6.2節「i/o models

」,stevens在這節中詳細說明了各種io的特點和區別,如果英文夠好的話,推薦直接閱讀。stevens的文風是有名的深入淺出,所以不用擔心看不懂。本文中的流程圖也是擷取自參考文獻。

stevens在文章中一共比較了五種io model:

blocking io

nonblocking io

io multiplexing

signal driven io

asynchronous io

由於signal driven io在實際中並不常用,所以我這只提及剩下的四種io model。

再說一下io發生時涉及的物件和步驟。以read函式舉例,對於乙個networkio會涉及到兩個系統物件,乙個是呼叫這個io的process (or thread),另乙個就是系統核心(kernel)。當乙個read操作發生時,它會經歷兩個階段:

(1)等待資料準備(waitingfor the data to be ready)

(2)將資料從核心拷貝到程序中(copyingthe data from the kernel to the process)

記住這兩點很重要,因為這些

io model

的區別就是在兩個階段上各有不同的情況。是否阻塞說的是第乙個階段,即等待資料準備階段是否會阻塞,而是否同步說的是第二階段,即將資料從核心拷貝到程序這個真實的

io operation

操作階段是否阻塞。

在linux中,預設情況下所有的socket都是blocking,乙個典型的讀操作流程大概是這樣:

當使用者程序呼叫了recvfrom這個系統呼叫,kernel就開始了io的第乙個階段:準備資料。對於network io來說,很多時候資料在一開始還沒有到達(比如,還沒有收到乙個完整的udp包),這個時候kernel就要等待足夠的資料到來。而在使用者程序這邊,整個程序會被阻塞。當kernel一直等到資料準備好了,它就會將資料從kernel中拷貝到使用者記憶體,然後kernel返回結果,使用者程序才解除block的狀態,重新執行起來。

所以,blocking io

的特點就是在

io執行的兩個階段都被

block

了。linux下,可以通過設定socket使其變為non-blocking。當對乙個non-blockingsocket執行讀操作時,流程是這個樣子:

從圖中可以看出,當使用者程序發出read操作時,如果kernel中的資料還沒有準備好,那麼它並不會block使用者程序,而是立刻返回乙個error。從使用者程序角度講,它發起乙個read操作後,並不需要等待,而是馬上就得到了乙個結果。使用者程序判斷結果是乙個error時,它就知道資料還沒有準備好,於是它可以再次傳送read操作。一旦kernel中的資料準備好了,並且又再次收到了使用者程序的system call,那麼它馬上就將資料拷貝到了使用者記憶體,然後返回。所以,使用者程序其實是需要不斷的主動詢問kernel資料好了沒有。

所以,non-blocking io

的特點就是在

io執行的第一階段不阻塞,若沒準備好即可返回,但是在第二階段即將資料從核心拷貝到程序這個真實的

io operation

操作階段會阻塞。

io multiplexing這個詞可能有點陌生,但是如果我說select/epoll,大概就都能明白了。有些地方也稱這種io方式為事件驅動io(event-driven io)。我們都知道,select/epoll的好處就在於單個process就可以同時處理多個網路連線的io。它的基本原理就是select/epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有資料到達了,就通知使用者程序。它的流程如圖:

當使用者程序呼叫了select,那麼整個程序會被block,而同時,kernel會「監視」所有select負責的socket,當任何乙個socket中的資料準備好了,select就會返回。這個時候使用者程序再呼叫read操作,將資料從kernel拷貝到使用者程序。

這個圖和blocking io的圖其實並沒有太大的不同,事實上,還更差一些。因為這裡需要使用兩個system call (select 和 recvfrom),而blocking io只呼叫了乙個system call (recvfrom)。但是,用select的優勢在於它可以同時處理多個connection。(多說一句。所以,如果處理的連線數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking io的web server效能更好,可能延遲還更大。select/epoll的優勢並不是對於單個連線能處理得更快,而是在於能處理更多的連線。)

在io multiplexingmodel中,實際中,對於每乙個socket,一般都設定成為non-blocking,但是,如上圖所示,整個使用者的process其實是一直被block的。只不過process是被select這個函式block,而不是被socket io給block。

所以多路復用

io的特點是引入了

select

這樣乙個過程,阻塞監視多個

socket

,一旦有某個

socket

資料準備好了,則返回執行同步io。 

linux下的asynchronousio其實用得很少。先看一下它的流程:

使用者程序發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到乙個asynchronous read之後,首先它會立刻返回,所以不會對使用者程序產生任何block。然後,kernel會等待資料準備完成,然後將資料拷貝到使用者記憶體,當這一切都完成之後,kernel會給使用者程序傳送乙個signal,告訴它read操作完成了。

所以非同步

io的特點是,在

io操作的兩個階段都不會阻塞,而是全權交給作業系統核心來完成,而核心完成後通過訊號來通知使用者程序即可。

區別在於

io操作的第一階段,呼叫

blockingio

會一直block

住對應的程序直到操作完成,而

non-blocking io

在kernel

還準備資料的情況下會立刻返回。

在說明synchronous io和asynchronousio的區別之前,需要先給出兩者的定義。stevens給出的定義(其實是posix的定義)是這樣子的:

a synchronous i/ooperation causes the requesting process to be blocked until that

i/o operation 

completes;

an asynchronous i/o operationdoes not cause the requesting process to be blocked;

兩者的區別就在於

synchronous io

做」io operation」

的時候會將

process

阻塞。按照這個定義,之前所述的

blockingio

,non-blocking io

,io multiplexing

都屬於synchronous io

。有人可能會說,non-blockingio並沒有被block啊。這裡有個非常「狡猾」的地方,定義中所指的」io operation」是指真實的io操作,就是例子中的recvfrom這個system call。non-blocking io在執行recvfrom這個system call的時候,如果kernel的資料沒有準備好,這時候不會block程序。但是,當kernel中資料準備好的時候,recvfrom會將資料從kernel拷貝到使用者記憶體中,這個時候程序是被block了,在這段時間內,程序是被block的。而asynchronous io則不一樣,當程序發起io 操作之後,就直接返回再也不理睬了,直到kernel傳送乙個訊號,告訴程序說io完成。在這整個過程中,程序完全沒有被block。

各個io model的比較如圖所示:

經過上面的介紹,會發現non-blockingio和asynchronous io的區別還是很明顯的。在non-blocking io中,雖然程序大部分時間都不會被block,但是它仍然要求程序去主動的check,並且當資料準備完成以後,也需要程序主動的再次呼叫recvfrom來將資料拷貝到使用者記憶體。而asynchronous io則完全不同。它就像是使用者程序將整個io操作交給了他人(kernel)完成,然後他人做完後發訊號通知。在此期間,使用者程序不需要去檢查io操作的狀態,也不需要主動的去拷貝資料。

阻塞式 非阻塞式IO

知識點 非阻塞式io 的兩種設定方法 1 函式fcntl 設定 o nonblock 選項 int flag fcntl sockfd,f getfl,0 檢查檔案標誌位 fcntl sockfd,f setfl,flag o nonblock 設定檔案標誌位 2 函式ioctl 設定fionbio...

阻塞I O,非阻塞I O

拿 socket舉例。當read資料時,如果這時沒有資料可讀,阻塞i o會一直等待有資料讀,資料從kernel copy 到socket的buffer後返回 非阻塞i o會立即返回,但如果有資料可讀,非阻塞i o也是等資料從kernel copy 到socket的buffer後返回。以上是阻塞與非阻...

非阻塞IO和阻塞IO

非阻塞io和阻塞io 在網路程式設計中對於乙個網路控制代碼會遇到阻塞io 和非阻塞io 的概念,這裡對於這兩種socket 先做一下說明 基本概念 阻塞io socket 的阻塞模式意味著必須要做完io 操作 包括錯誤 才會返回。非阻塞io 非阻塞模式下無論操作是否完成都會立刻返回,需要通過其他方式...