聊聊同步、異步、阻塞與非阻塞

2020-09-23 Hu先生Linux後臺開發


聊聊同步、異步、阻塞與非阻塞

近來遇到了一些常見的概念,尤其是網絡編程方面的概念,如:阻塞、非阻塞、異步I/O等等,對於這些概念自己也沒有太清晰的認識,只是很模糊的概念,說了解吧也了解,但是要讓自己準確的描述概念方面的具體細節,卻說的不那麼準確,這也是自己在這幾個方面也沒有細細考究過的原因吧。經過看了些這幾個概念的資料,發現同步、異步、阻塞、非阻塞的概念其實也並不難以理解,在此寫下此文,歡迎拍磚,希望多多交流。

1 同步與異步

首先來解釋同步和異步的概念,這兩個概念與消息的通知機制有關。也就是同步與異步主要是從消息通知機制角度來說的。

1.1 概念描述

所謂同步就是一個任務的完成需要依賴另外一個任務時,只有等待被依賴的任務完成後,依賴的任務才能算完成,這是一種可靠的任務序列。要麼成功都成功,失敗都失敗,兩個任務的狀態可以保持一致。

所謂異步是不需要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工作,依賴的任務也立即執行,只要自己完成了整個任務就算完成了。至於被依賴的任務最終是否真正完成,依賴它的任務無法確定,所以它是不可靠的任務序列。

1.2 消息通知

異步的概念和同步相對。當一個同步調用發出後,調用者要一直等待返回消息(結果)通知後,才能進行後續的執行;當一個異步過程調用發出後,調用者不能立刻得到返回消息(結果)。實際處理這個調用的部件在完成後,通過狀態、通知和回調來通知調用者。

這裡提到執行部件和調用者通過三種途徑返回結果:狀態、通知和回調。使用哪一種通知機制,依賴於執行部件的實現,除非執行部件提供多種選擇,否則不受調用者控制。

如果執行部件用狀態來通知,那麼調用者就需要每隔一定時間檢查一次,效率就很低(有些初學多線程編程的人,總喜歡用一個循環去檢查某個變量的值,這其實是一種很嚴重的錯誤);

如果是使用通知的方式,效率則很高,因為執行部件幾乎不需要做額外的操作。至於回調函數,其實和通知沒太多區別。

1.2 場景比喻

舉個例子,比如我去銀行辦理業務,可能會有兩種方式:

選擇排隊等候;

另種選擇取一個小紙條上面有我的號碼,等到排到我這一號時由櫃檯的人通知我輪到我去辦理業務了;

第一種:前者(排隊等候)就是同步等待消息通知,也就是我要一直在等待銀行辦理業務情況;

第二種:後者(等待別人通知)就是異步等待消息通知。在異步消息處理中,等待消息通知者(在這個例子中就是等待辦理業務的人)往往註冊一個回調機制,在所等待的事件被觸發時由觸發機制(在這裡是櫃檯的人)通過某種機制(在這裡是寫在小紙條上的號碼,喊號)找到等待該事件的人。

2 阻塞與非阻塞

阻塞和非阻塞這兩個概念與程序(線程)等待消息通知(無所謂同步或者異步)時的狀態有關。也就是說阻塞與非阻塞主要是程序(線程)等待消息通知時的狀態角度來說的。

2.1 概念描述

阻塞調用是指調用結果返回之前,當前線程會被掛起,一直處於等待消息通知,不能夠執行其他業務。函數只有在得到結果之後才會返回。

有人也許會把阻塞調用和同步調用等同起來,實際上它們是不同的。

對於同步調用來說,很多時候當前線程可能還是激活的,只是從邏輯上當前函數沒有返回而已,此時,這個線程可能也會處理其他的消息。還有一點,在這裡先擴展下:
(a) 如果這個線程在等待當前函數返回時,仍在執行其他消息處理,那這種情況就叫做同步非阻塞;

(b) 如果這個線程在等待當前函數返回時,沒有執行其他消息處理,而是處於掛起等待狀態,那這種情況就叫做同步阻塞;

所以同步的實現方式會有兩種:同步阻塞、同步非阻塞;同理,異步也會有兩種實現:異步阻塞、異步非阻塞;

對於阻塞調用來說,則當前線程就會被掛起等待當前函數返回;
非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函數不會阻塞當前線程,而會立刻返回。雖然表面上看非阻塞的方式可以明顯的提高CPU的利用率,但是也帶了另外一種後果就是系統的線程切換增加。增加的CPU執行時間能不能補償系統的切換成本需要好好評估。

2.2 場景比喻

繼續上面的那個例子,不論是排隊還是使用號碼等待通知,如果在這個等待的過程中,等待者除了等待消息通知之外不能做其它的事情,那麼該機制就是阻塞的,表現在程序中,也就是該程序一直阻塞在該函數調用處不能繼續往下執行。

相反,有的人喜歡在銀行辦理這些業務的時候一邊打打電話發發簡訊一邊等待,這樣的狀態就是非阻塞的,因為他(等待者)沒有阻塞在這個消息通知上,而是一邊做自己的事情一邊等待。

但是需要注意了,同步非阻塞形式實際上是效率低下的,想像一下你一邊打著電話一邊還需要抬頭看到底隊伍排到你了沒有。如果把打電話和觀察排隊的位置看成是程序的兩個操作的話,這個程序需要在這兩種不同的行為之間來回的切換,效率可想而知是低下的;而異步非阻塞形式卻沒有這樣的問題,因為打電話是你(等待者)的事情,而通知你則是櫃檯(消息觸發機制)的事情,程序沒有在兩種不同的操作中來回切換。

3 同步/異步與阻塞/非阻塞

同步阻塞形式
效率是最低的,

拿上面的例子來說,就是你專心排隊,什麼別的事都不做。

實際程序中:就是未對fd 設置O_NONBLOCK標誌位的read/write 操作;

異步阻塞形式
如果在銀行等待辦理業務的人採用的是異步的方式去等待消息被觸發(通知),也就是領了一張小紙條,假如在這段時間裡他不能離開銀行做其它的事情,那麼很顯然,這個人被阻塞在了這個等待的操作上面;

異步操作是可以被阻塞住的,只不過它不是在處理消息時阻塞,而是在等待消息通知時被阻塞。

比如select 函數,假如傳入的最後一個timeout參數為NULL,那麼如果所關注的事件沒有一個被觸發,程序就會一直阻塞在這個select 調用處。

同步非阻塞形式
實際上是效率低下的,

想像一下你一邊打著電話一邊還需要抬頭看到底隊伍排到你了沒有,如果把打電話和觀察排隊的位置看成是程序的兩個操作的話,這個程序需要在這兩種不同的行為之間來回的切換,效率可想而知是低下的。

很多人會寫阻塞的read/write 操作,但是別忘了可以對fd設置O_NONBLOCK 標誌位,這樣就可以將同步操作變成非阻塞的了。

異步非阻塞形式
效率更高,

因為打電話是你(等待者)的事情,而通知你則是櫃檯(消息觸發機制)的事情,程序沒有在兩種不同的操作中來回切換。

比如說,這個人突然發覺自己菸癮犯了,需要出去抽根煙,於是他告訴大堂經理說,排到我這個號碼的時候麻煩到外面通知我一下(註冊一個回調函數),那麼他就沒有被阻塞在這個等待的操作上面,自然這個就是異步+非阻塞的方式了。

如果使用異步非阻塞的情況,比如aio_*組的操作,當發起一個aio_read操作時,函數會馬上返回不會被阻塞,當所關注的事件被觸發時會調用之前註冊的回調函數進行處理。

很多人會把同步和阻塞混淆,我想是因為很多時候同步操作會以阻塞的形式表現出來,比如很多人會寫阻塞的read/write操作,但是別忘了可以對fd設置O_NONBLOCK標誌位,這樣就可以將同步操作變成非阻塞的了。但最根本是因為沒有區分這兩個概念,比如阻塞的read/write操作中,其實是把消息通知機制和等待消息通知的狀態結合在了一起,在這裡所關注的消息就是fd是否可讀/寫,而等待消息通知的狀態則是對fd可讀/寫等待過程中程序(線程)的狀態。當我們將這個fd設置為非阻塞的時候,read/write操作就不會再等待消息通知這裡阻塞,如果fd不可讀/寫則操作立即返回。

同樣的,很多人也會把異步和非阻塞混淆,因為異步操作一般都不會在真正的IO操作處被阻塞,比如如果用select函數,當select返回可讀時再去read一般都不會被阻塞,而是在select函數調用處阻塞。

4 小明的故事

對上面所講的概念再次進行一個場景梳理,上面已經明確說明,同步/異步關注的是消息通知的機制,而阻塞/非阻塞關注的是程序(線程)等待消息通知時的狀態。以小明下載文件打個比方,從這兩個關注點來再次說明這兩組概念,希望能夠更好的促進大家的理解。

同步阻塞:小明一直盯著下載進度條,到 100% 的時候就完成。
同步體現在:等待下載完成通知;

阻塞體現在:等待下載完成通知過程中,不能做其他任務處理;

同步非阻塞:小明提交下載任務後就去幹別的,每過一段時間就去瞄一眼進度條,看到 100% 就完成。
同步體現在:等待下載完成通知;

非阻塞體現在:等待下載完成通知過程中,去幹別的任務了,只是時不時會瞄一眼進度條;【小明必須要在兩個任務間切換,關注下載進度】

異步阻塞:小明換了個有下載完成通知功能的軟體,下載完成就「叮」一聲。不過小明仍然一直等待「叮」的聲音(看起來很傻,不是嗎)。
異步體現在:下載完成「叮」一聲通知;

阻塞體現在:等待下載完成「叮」一聲通知過程中,不能做其他任務處理;

異步非阻塞:仍然是那個會「叮」一聲的下載軟體,小明提交下載任務後就去幹別的,聽到「叮」的一聲就知道完成了。
異步體現在:下載完成「叮」一聲通知;

非阻塞體現在:等待下載完成「叮」一聲通知過程中,去幹別的任務了,只需要接收「叮」聲通知即可;【軟體處理下載任務,小明處理其他任務,不需關注進度,只需接收軟體「叮」聲通知,即可】

也就是說,同步/異步是「下載完成消息」通知的方式(機制),而阻塞/非阻塞則是在等待「下載完成消息」通知過程中的狀態(能不能幹其他任務),在不同的場景下,同步/異步、阻塞/非阻塞的四種組合都有應用。

所以,綜上所述,同步和異步僅僅是關注的消息如何通知的機制,而阻塞與非阻塞關注的是等待消息通知時的狀態。也就是說,同步的情況下,是由處理消息者自己去等待消息是否被觸發,而異步的情況下是由觸發機制來通知處理消息者,所以在異步機制中,處理消息者和觸發機制之間就需要一個連接的橋梁:

在銀行的例子中,這個橋梁就是小紙條上面的號碼。

在小明的例子中,這個橋梁就是軟體「叮」的聲音。

最後,請大家注意理解「消息通知機制」和「等待消息通知時的狀態」這兩個概念,這是理解四個概念的關鍵所在。

相關焦點

  • 同步,異步,阻塞,非阻塞
    同步與異步關注的是消息通知的機制,而阻塞與非阻塞關注的是程序(線程)等待消息通知時的狀態。以下載文件打個比方。同步阻塞:一直盯著下載進度條,到 100% 的時候就完成。同步體現在:等待下載完成通知;阻塞體現在:等待下載完成通知過程中,不能做其他任務處理;同步非阻塞:提交下載任務後就去幹別的,每過一段時間就去看一眼進度條,當是 100% 就完成。
  • 概念辨析:同步/異步,阻塞/非阻塞
    同步與異步同步與異步要從服務調用方的視角區分.阻塞與非阻塞阻塞與非阻塞是針對 CPU 而言的.以上面的例子繼續談, 當調用方調用一個同步 API 時, 在等待服務方返回結果期間,如果調用方的 CPU 也陷入等待, 那麼這就是阻塞的;如果調用的 CPU 掛起, 趁機去執行其他的操作, 那麼這就是非阻塞的.
  • 重新認識同步與異步,阻塞和非阻塞的概念
    重新認識同步與異步,阻塞和非阻塞的概念前言在實際的開發中,我們經常會聽到同步,異步,阻塞,非阻塞這些編程概念,每次遇到的時候都會蒙圈,然後就各種查網上似是而非的資料,結果越查越迷糊,大部分文章都千篇一律,沒有說到本質上的區別,所以下次再碰到這些概念,印象還是比較模糊
  • 一篇文章講解同步/異步,阻塞,非阻塞
    同步/異步,阻塞/非阻塞一篇文章徹底弄懂os #作業系統 課程開始之前,我們先看一個假設:小明有一天和他的母親,王叔叔在家,小明母親讓小明去小區門口看著他的父親是否開車回來,如果開車回來,那麼就給他的母親打個電話。
  • 什麼是Node.js的阻塞與非阻塞、同步與異步?
    你可能已經聽說Node.js是「基於Chrome的V8 JavaScript引擎的異步JavaScript運行的」,並且它「使用事件驅動的非阻塞I / O模型,使其輕量級和高效」。但對某些人來說,這不是最好的解釋,或許太過於概念化。首先要了解什麼是Node.js?
  • Python基礎必備知識:同步異步阻塞非阻塞
    在異步消息處理中,等待消息通知者(在這個例子中就是等待辦理業務的人)往往註冊一個回調機制,在所等待的事件被觸發時由觸發機制(在這裡是櫃檯的人)通過某種機制(在這裡是寫在小紙條上的號碼,喊號)找到等待該事件的人。三、阻塞和非阻塞阻塞和非阻塞這兩個概念與程序(線程)等待消息通知(無所謂同步或者異步)時的狀態有關。
  • 「漫畫」「同步異步阻塞非阻塞」
    本文轉載自【微信公眾號:小碼逆襲,ID:gh_7c5a039380a0】經微信公眾號授權轉載,如需轉載與原文作者聯繫我相信很多人看到這四個詞語:阻塞、非阻塞、同步、異步都很容易混淆,傻傻分不清楚,這四個詞又是面試中面試官很喜歡問的,當初我在面試騰訊的時候就被面試官追著問同步和阻塞的區別
  • NIO、BIO、AIO、同步異步、阻塞非阻塞傻傻分不清楚?
    阻塞IO非阻塞IO同步與異步同步與異步是基於應用程式和作業系統處理IO事件所採用的方式:同步:應用程式要直接參與IO讀寫的操作。異步:所有的IO操作交給作業系統去處理,應用程式只需要等待通知。同步IO異步IONIO與BIO的區別總結NIOBIO基於緩衝區( Buffer )基於流( Stream )非阻塞 IO阻塞 IO選擇器( Selector )無BIOBlocking IO,是同步阻塞的IO模型,傳統的IO【java.io包】就是這種模型。
  • IO復用,AIO,BIO,NIO同步,異步,阻塞和非阻塞區別
    二、同步異步,阻塞非阻塞區別聯繫 實際上同步與異步是針對應用程式與內核的交互而言的。同步過程中進程觸發IO操作並等待(也就是我們說的阻塞)或者輪詢的去查看IO操作(也就是我們說的非阻塞)是否完成。 異步過程中進程觸發IO操作以後,直接返回,做自己的事情,IO交給內核來處理,完成後內核通知進程IO完成。
  • Nginx專題之-一文就懂同步和異步阻塞和非阻塞(深入才更懂得)
    、同步與異步的知識。01阻塞與非阻塞阻塞和非阻塞主要是指作業系統或底層的C庫提供的方法或系統的調用,我們調用這些方法可能會導致我的進程sleep的狀態。為什麼會進入sleep狀態,應為當前的條件無法滿足,作業系統會主動把我的進程切換到另外一個進程上繼續等待,這就是一種阻塞的方法。
  • 如何解讀 Java IO、NIO 中的同步阻塞與同步非阻塞?
    但是,對於同步阻塞、同步非阻塞、異步這些概念,還是比較的模糊,一直處於似懂非懂的狀態。所以這兩天,一直在網上看看大家對此的評論,也得到了一些啟發。三、Java 中 IO 和 NIO我們都知道 Java 中:IO 是同步阻塞,而 NIO 是同步非阻塞;而經過上面關於 Liunx 網絡 I/O 模型的解讀,我們都已經比較清楚地了解了同步異步和阻塞非阻塞的概念。
  • Java Sockets I/O: 阻塞、非阻塞與異步(二)
    譯者註:作為一個沒學過作業系統知識而直接學編程的人,對於阻塞、非阻塞、異步的認知,總感覺隔著一層紗。自上次翻譯過 《Event Loop 解疑》 後,它讓我對異步的理解深入了一些,趁熱打鐵,再譯一篇相關的主題。文章比較長,將分為多篇展開。
  • socket阻塞和非阻塞的區別
    ,我們常常見到同步、異步、阻塞和非阻塞四種調用方式。       同步 所謂同步,就是在發出一個功能調用時,在沒有得到結果之前,該調用就不返回。按照這個定義,其實絕大多數函數都是同步調用(例如sin, isdigit等)。但是一般而言,我們在說同步、異步的時候,特指那些需要其他部件協作或者需要一定時間完成的任務。最常見的例子就是 SendMessage。該函數發送一個消息給某個窗口,在對方處理完消息之前,這個函數不返回。
  • Kafka Producer 異步發送消息居然也會阻塞?
    Kafka 一直以來都以高吞吐量的特性而家喻戶曉,就在上周,在一個性能監控項目中,需要使用到 Kafka 傳輸海量消息,在這過程中遇到了一個 Kafka Producer 異步發送消息會被阻塞的問題,導致生產端發送耗時很大。
  • Python進程池(阻塞和異步非阻塞)
    第四個任務等待(進程池已滿)第一個任務結束,第四個任務開始Pool類的實例方法(apply與apply_async):pool.apply_async()apply主進程會被阻塞到函數執行結束apply_async 是異步非阻塞的意思就是:不用等待當前進程執行完畢,隨時根據系統調度來進行進程切換。我們進入看源碼,可以發現我們是要傳入一個函數名,而不是一個函數,參數就用tuple,或者字典。
  • 一代更比一代強,異步非阻塞的藝術,還是python頭髮掉的少
    >老猿猿應該都用過guava的ListenableFuture,現在大家都用CompletableFuture了使用案例地址(並發處理任務,並且匯總結果後統一處理數據):https://download.csdn.net/download/u011643716/12828671JDK1.8中的CompletableFuture為我們提供了異步函數式編程
  • 精講響應式WebClient,GET請求阻塞與非阻塞調優詳解
    精講響應式webclient第1篇-響應式非阻塞IO與基礎用法在上一篇文章為大家介紹了響應式IO模型和WebClient的基本用法。本節來繼續深入的為大家介紹:如何使用WebClient作為Http客戶端發送GET請求與進行響應結果的接收。
  • 從PHP-FPM、NGINX的請求處理來學習阻塞與非阻塞
    php-fpm.confI/O阻塞每個什麼是阻塞非阻塞舉個慄子來說,早上起床了你要去煮麵,面煮上了如果你一直在鍋前等著那你就阻塞了,因為你得看著面不能幹別的事,其他的事得煮完面再去做。你->煮麵->其他的事這是阻塞這次你學聰明了,煮上面然後去刷牙了,面自己煮著就行了,刷完牙回來再看面,還沒好你又去洗臉了,再回來看,面好了,吃飯。你->煮麵->刷牙->煮麵->洗臉->煮麵 同樣的時間多幹了兩個事,這是非阻塞。當然實際的情況跟這個例子差一點。但大體意思是差不多的。
  • 聊聊工作中經常遇到的「異步」
    簡單的說,異步是扔出去一段數據,對方靠著內容前後所檢查到的特殊性發現了它,把這個內容存下來;而同步通信是對方在時刻等著發送方發號施令,發送方告訴對方要發送了,然後雙方一拍即合。從通信效率來看,同步通信效率高,異步通信效率較低。但從實現方式來看,同步通信較複雜,異步通信相對簡單,計算機的接口大多是異步的。
  • 同步與異步Python有何不同?
    術語「同步」和「異步」指的是編寫並發應用程式的兩種方式。所謂的「同步」伺服器使用底層作業系統支持的線程和進程來實現這種並發性。當一個任務需要等待一個外部事件(例如,一個資料庫伺服器的響應)時,不會像一個同步的 worker 那樣等待,而是會告訴循環它需要等待什麼,然後將控制權返回給它。循環就能夠在這個任務被資料庫阻塞的時候發現另外一個準備就緒的任務。最終,資料庫將發送一個響應,而那時循環會認為第一個的任務已經準備好再次運行,並將儘快恢復它。異步任務暫停和恢復執行的這種能力可能在抽象上很難理解。