Linux IO - 同步,异步,阻塞,非阻塞
From:http://blog.csdn.net/historyasamirror/article/details/5778378
同步/異步,阻塞/非阻塞概念深度解析:http://blog.csdn.net/lengxiao1993/article/details/78154467
知乎上關于?阻塞和非阻塞、同步和異步 之間區別的生動解釋。
https://www.zhihu.com/question/19732473
“阻塞”與"非阻塞"與"同步"與“異步"不能簡單的從字面理解,提供一個從分布式系統角度的回答。
1.同步與異步
? ? ? ??同步與異步在訪問數據的方式不同,同步需要主動讀寫數據,在讀寫數據的過程中還是會阻塞;異步只需要I/O操作完成的通知,并不主動讀寫數據,由操作系統內核完成數據的讀寫。
? ? ? ??同步和異步關注的是消息通信機制 (synchronous communication/ asynchronous communication)。
? ? ? ? 所謂同步,就是在發出一個*調用*時,在沒有得到結果之前,該*調用*就不返回。但是一旦調用返回,就得到返回值了。換句話說,就是由*調用者*主動等待這個*調用*的結果。
? ? ? ? 而異步則是相反,*調用*在發出之后,這個調用就直接返回了,所以沒有返回結果。換句話說,當一個異步過程調用發出后,調用者不會立刻得到結果。而是在*調用*發出后,*被調用者*通過狀態、通知來通知調用者,或通過回調函數處理這個調用。
? ? ? ? 典型的異步編程模型比如Node.js
? ? ? ? 舉個通俗的例子:你打電話問書店老板有沒有《分布式系統》這本書,如果是同步通信機制,書店老板會說,你稍等,”我查一下",然后開始查啊查,在書店老板查詢這期間,你一直沒掛電話并且一直在等待書店老板告訴你結果。等查好了(可能是5秒,也可能是一天)告訴你結果(返回結果),然后你掛掉電話。而異步通信機制,書店老板直接告訴你我查一下啊,查好了打電話給你,然后直接掛電話了(不返回結果)。然后查好了,他會主動打電話給你。在這里老板通過“回電”這種方式來回調。
? ? ? ? 另一個例子來說明同步異步: 某業務需要甲乙甚至多方合作的時候。總是按照“甲方請求一次,乙方應答一次”這樣的有序序列處理業務,只有當“一次請求一次應答”的過程 結束才可以發生下一次的“一次請求一次應答”,那么就說他們采用的是同步。(同步IO中,對同一個描述符的操作必須是有序的)
? ? ? ? 如果甲方只要有需要,就會發送請求,不管上次請求有沒有得到乙方應答。而乙方只要甲方有請求就會接受,不是等這次請求處理完畢再接受甲方新請求。這樣請求應答分開的序列,就可以認為是異步。異步情況下,請求和應答不需要一致進行,可能甲方后請求的業務,卻先得到乙方的應答。同步是線性的,而異步可以認為是并發的。(異步IO中,異步IO可以允許多方同時對同一個描述符發送IO請求,或者一次發多個請求,當然有機制保證如何區分這些請求,)
2. 阻塞與非阻塞
? ? ? ? 阻塞非阻塞:?進程/線程要訪問的數據是否就緒,進程/線程是否需要等待。如果請求不能立即得到應答,需要等待,那就是阻塞;否則可以理解為非阻塞。
? ? ? ??阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態。阻塞調用是指調用結果返回之前,當前線程會被掛起。調用線程只有在得到結果之后才會返回。非阻塞調用指在不能立刻得到結果之前,該調用不會阻塞當前線程。還是上面的例子,你打電話問書店老板有沒有《分布式系統》這本書,你如果是阻塞式調用,你會一直把自己“掛起”,直到得到這本書有沒有的結果,如果是非阻塞式調用,你不管老板有沒有告訴你,你自己先一邊去玩了, 當然你也要偶爾過幾分鐘check一下老板有沒有返回結果。在這里阻塞與非阻塞與是否同步異步無關。跟老板通過什么方式回答你結果無關。
同步與異步
實際上同步與異步是針對應用程序與內核的交互而言的。同步過程中進程觸發IO操作并等待或者輪詢的去查看IO操作是否完成。異步過程中進程觸發IO操作以后,直接返回,做自己的事情,IO交給內核來處理,完成后內核通知進程IO完成。
阻塞與非阻塞
簡單理解為需要做一件事能不能立即得到返回應答,如果不能立即獲得返回,需要等待,那就阻塞了,否則就可以理解為非阻塞。
阻塞 / 非阻塞描述的是函數,指訪問某個函數時是否會阻塞線程(block,線程進入阻塞狀態)。
同步 / 異步描述的是執行IO操作的主體是誰,同步是由用戶進程自己去執行最終的IO操作。異步是用戶進程自己不關系實際IO操作的過程,只需要由內核在IO完成后通知它既可,由內核進程來執行最終的IO操作。
同步IO異步IO阻塞IO非阻塞IO
同步(synchronous) IO 和 異步(asynchronous) IO,阻塞(blocking) IO 和 非阻塞(non-blocking)IO 分別是什么,到底有什么區別?這個問題其實不同的人給出的答案都可能不同,比如wiki,就認為asynchronous IO和non-blocking IO是一個東西。這其實是因為不同的人的知識背景不同,并且在討論這個問題的時候上下文(context)也不相同。所以,為了更好的回答這個問題,我先限定一下本文的上下文。
本文討論的背景是Linux環境下的network IO。
本文最重要的參考文獻是Richard Stevens的“UNIX? Network Programming Volume 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發生時涉及的對象和步驟。
對于一個network IO (這里我們以read舉例),它會涉及到兩個系統對象,一個是調用這個IO的process (or thread),另一個就是系統內核(kernel)。
當一個read操作發生時,它會經歷兩個階段:
記住這兩點很重要,因為這些IO Model的區別就是在兩個階段上各有不同的情況。
blocking IO
在linux中,默認情況下所有的socket都是blocking,一個典型的讀操作流程大概是這樣:
當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:準備數據。對于network io來說,很多時候數據在一開始還沒有到達(比如,還沒有收到一個完整的UDP包),這個時候kernel就要等待足夠的數據到來。而在用戶進程這邊,整個進程會被阻塞。當kernel一直等到數據準備好了,它就會將數據從kernel中拷貝到用戶內存,然后kernel返回結果,用戶進程才解除block的狀態,重新運行起來。
所以,blocking IO的特點就是在IO執行的兩個階段都被block了。
下面以一個生動形象的例子來說明這四個概念。周末我和女友去逛街,中午餓了,我們準備去吃飯。周末人多,吃飯需要排隊,我和女友有以下幾種方案:
(1)我和女友點完餐后,不知道什么時候能做好,只好坐在餐廳里面等,直到做好,然后吃完才離開。女友本想還和我一起逛街的,但是不知道飯能什么時候做好,只好和我一起在餐廳等,而不能去逛街,直到吃完飯才能去逛街,中間等待做飯的時間浪費掉了。這就是典型的阻塞。網絡中IO阻塞如下圖所示:
non-blocking IO
linux下,可以通過設置socket使其變為non-blocking。當對一個non-blocking socket執行讀操作時,流程是這個樣子:
從圖中可以看出,當用戶進程發出read操作時,如果kernel中的數據還沒有準備好,那么它并不會block用戶進程,而是立刻返回一個error。從用戶進程角度講 ,它發起一個read操作后,并不需要等待,而是馬上就得到了一個結果。用戶進程判斷結果是一個error時,它就知道數據還沒有準備好,于是它可以再次發送read操作。一旦kernel中的數據準備好了,并且又再次收到了用戶進程的system call,那么它馬上就將數據拷貝到了用戶內存,然后返回。
所以,用戶進程其實是需要不斷的主動詢問kernel數據好了沒有。即需要不斷的? ?輪詢? ?kernel 數據好了沒有
(2)接上面例子。我女友不甘心白白在這等,又想去逛商場,又擔心飯好了。所以我們逛一會,回來詢問服務員飯好了沒有,來來回回好多次,飯都還沒吃都快累死了啦。這就是非阻塞。需要不斷的詢問,是否準備好了。網絡IO非阻塞如下圖所示:
IO multiplexing
IO multiplexing這個詞可能有點陌生,但是如果我說select,epoll,大概就都能明白了。有些地方也稱這種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 multiplexing Model中,實際中,對于每一個socket,一般都設置成為non-blocking,但是,如上圖所示,整個用戶的process其實是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。
(3)接上面例子。與第二個方案差不多,餐廳安裝了電子屏幕用來顯示點餐的狀態,這樣我和女友逛街一會,回來就不用去詢問服務員了,直接看電子屏幕就可以了。這樣每個人的餐是否好了,都直接看電子屏幕就可以了,這就是典型的IO多路復用,如select、poll、epoll。網絡IO具體模型如下圖所示:
Asynchronous I/O
linux下的asynchronous IO其實用得很少。先看一下它的流程:
用戶進程發起read操作之后,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到一個asynchronous read之后,首先它會立刻返回,所以不會對用戶進程產生任何block。然后,kernel會等待數據準備完成,然后將數據拷貝到用戶內存,當這一切都完成之后,kernel會給用戶進程發送一個signal,告訴它read操作完成了。
(4)接上面例子。女友不想逛街,又餐廳太吵了,回家好好休息一下。于是我們叫外賣,打個電話點餐,然后我和女友可以在家好好休息一下,飯好了送貨員送到家里來。這就是典型的異步,只需要打個電話說一下,然后可以做自己的事情,飯好了就送來了。linux提供了AIO庫函數實現異步,但是用的很少。目前有很多開源的異步IO庫,例如libevent、libev、libuv。異步過程如下圖所示:
到目前為止,已經將四個IO Model都介紹完了。現在回過頭來回答最初的那幾個問題:blocking和non-blocking的區別在哪,synchronous IO和asynchronous IO的區別在哪。
先回答最簡單的這個:blocking vs non-blocking。前面的介紹中其實已經很明確的說明了這兩者的區別。調用blocking IO會一直block住對應的進程直到操作完成,而non-blocking IO在kernel還準備數據的情況下會立刻返回。
在說明synchronous IO和asynchronous IO的區別之前,需要先給出兩者的定義。Stevens給出的定義(其實是POSIX的定義)是這樣子的:
??? A synchronous I/O operation causes the requesting process to be blocked until that?I/O operation completes;
? ? 一個同步 I/O 操作會引起請求處理被阻塞 至到 I/O 操作完成,阻塞解除。
??? An asynchronous I/O operation does not cause the requesting process to be blocked;
? ? 一個異步 I/O 操作不會引起請求處理被阻塞
兩者的區別就在于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在執行recvfrom這個system call的時候,如果kernel的數據沒有準備好,這時候不會block進程。但是,當kernel中數據準備好的時候,recvfrom會將數據從kernel拷貝到用戶內存中,這個時候進程是被block了,在這段時間內,進程是被block的。而asynchronous IO則不一樣,當進程發起IO 操作之后,就直接返回再也不理睬了,直到kernel發送一個信號,告訴進程說IO完成。在這整個過程中,進程完全沒有被block。
各個IO Model的比較如圖所示:
經過上面的介紹,會發現non-blocking IO和asynchronous IO的區別還是很明顯的。在non-blocking IO中,雖然進程大部分時間都不會被block,但是它仍然要求進程去主動的check,并且當數據準備完成以后,也需要進程主動的再次調用recvfrom來將數據拷貝到用戶內存。而asynchronous IO則完全不同。它就像是用戶進程將整個IO操作交給了他人(kernel)完成,然后他人做完后發信號通知。在此期間,用戶進程不需要去檢查IO操作的狀態,也不需要主動的去拷貝數據。
最后,再舉幾個不是很恰當的例子來說明這四個IO Model:
有A,B,C,D四個人在釣魚:
A用的是最老式的魚竿,所以呢,得一直守著,等到魚上鉤了再拉桿;
B的魚竿有個功能,能夠顯示是否有魚上鉤,所以呢,B就和旁邊的MM聊天,隔會再看看有沒有魚上鉤,有的話就迅速拉桿;
C用的魚竿和B差不多,但他想了一個好辦法,就是同時放好幾根魚竿,然后守在旁邊,一旦有顯示說魚上鉤了,它就將對應的魚竿拉起來;
D是個有錢人,干脆雇了一個人幫他釣魚,一旦那個人把魚釣上來了,就給D發個短信。
總結
以上是生活随笔為你收集整理的Linux IO - 同步,异步,阻塞,非阻塞的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Jupyter 安装使用
- 下一篇: 不要二