1. 程式人生 > >深入理解併發/並行,阻塞/非阻塞,同步/非同步 同步與阻塞,非同步與非阻塞的區別

深入理解併發/並行,阻塞/非阻塞,同步/非同步 同步與阻塞,非同步與非阻塞的區別

同步與非同步是對應的,它們是執行緒之間的關係,兩個執行緒之間要麼是同步的,要麼是非同步的。

阻塞與非阻塞是對同一個執行緒來說的,在某個時刻,執行緒要麼處於阻塞,要麼處於非阻塞。

阻塞是使用同步機制的結果,非阻塞則是使用非同步機制的結果。

深入理解併發/並行,阻塞/非阻塞,同步/非同步

1. 阻塞,非阻塞

首先,阻塞這個詞來自作業系統的執行緒/程序的狀態模型中,如下圖:

  程序狀態

一個執行緒/程序經歷的5個狀態,建立,就緒,執行,阻塞,終止。各個狀態的轉換條件如上圖,其中有個阻塞

狀態,就是說當執行緒中呼叫某個函式,需要IO請求,或者暫時得不到競爭資源的,作業系統會把該執行緒阻塞起來,避免浪費CPU資源,等到得到了資源,再變成就緒狀態,等待CPU排程執行。

定義:
阻塞呼叫是指呼叫結果返回之前,呼叫者會進入阻塞狀態等待。只有在得到結果之後才會返回。
非阻塞呼叫是指在不能立刻得到結果之前,該函式不會阻塞當前執行緒,而會立刻返回。

阻塞呼叫:比如 socket 的 recv(),呼叫這個函式的執行緒如果沒有資料返回,它會一直阻塞著,也就是 recv() 後面的程式碼都不會執行了,程式就停在 recv() 這裡等待,所以一般把 recv() 放在單獨的執行緒裡呼叫。

非阻塞呼叫:比如非阻塞socket 的 send(),呼叫這個函式,它只是把待發送的資料複製到TCP輸出緩衝區中,就立刻返回了,執行緒並不會阻塞,資料有沒有發出去 send() 是不知道的,不會等待它發出去才返回的。

拓展

如果執行緒始終阻塞著,永遠得不到資源,於是就發生了死鎖
比如A執行緒要X,Y資源才能繼續執行,B執行緒也要X,Y資源才能執行,但X,Y同時只能給一個執行緒用(即互斥條件)用的時候其他執行緒又不能搶奪。
A 有 X,等待 Y。
B 有 Y,等待 X。
於是A,B發生了迴圈等待,造成死鎖。給使用者的感覺就是程式卡著不動了。
在寫程式碼的時候要特別注意共享資源的使用,用訊號量控制好,避免造成死鎖。死鎖的解除有個著名的

銀行家演算法

阻塞和掛起:阻塞是被動的,比如搶不到資源。掛起是主動的,執行緒自己呼叫 suspend() 把自己退出執行態了,某些時候呼叫 resume() 又恢復執行。

執行緒執行完就會被銷燬,如果不想執行緒被頻繁的建立,銷燬,怎麼辦?可以給執行緒裡面寫個死迴圈,或者讓執行緒有任務的時候執行,沒任務的時候掛起,就像iOS中的 runloop 機制一樣。執行緒就不會隨便的終止了。


2. 同步,非同步

定義
同步:在發出一個同步呼叫時,在沒有得到結果之前,該呼叫就不返回。
非同步:在發出一個非同步呼叫後,呼叫者不會立刻得到結果,該呼叫就返回了。

同步例子

int n = func();
next();
// func() 的結果沒有返回,next() 就不會執行,直到 func() 執行完。

非同步例子

func(callback);
next();
...

void callback(int n) // func 結果回撥 { int k = n; } // func() 執行後,還沒得出結果就立即返回,然後執行 next() 了 // 等到結果出來,func() 回撥 callback() 通知呼叫者結果。 

同步的定義看起來跟阻塞很像,但是同步跟阻塞是兩個概念,同步呼叫的時候,執行緒不一定阻塞,呼叫雖然沒返回,但它還是在執行狀態中的,CPU很可能還在執行這段程式碼,而阻塞的話,它就肯定不在CPU中跑這個程式碼了。這就是同步和阻塞的區別。同步是肯定可以在,阻塞是肯定不在。

非同步和非阻塞的定義比較像,兩者的區別是非同步是說呼叫的時候結果不會馬上返回,執行緒可能被阻塞起來,也可能不阻塞,兩者沒關係。非阻塞是說呼叫的時候,執行緒肯定不會進入阻塞狀態。

上面兩組概念,就有4種組合。
同步阻塞呼叫:得不到結果不返回,執行緒進入阻塞態等待。
同步非阻塞呼叫:得不到結果不返回,執行緒不阻塞一直在CPU執行。
非同步阻塞呼叫:去到別的執行緒,讓別的執行緒阻塞起來等待結果,自己不阻塞。
非同步非阻塞呼叫:去到別的執行緒,別的執行緒一直在執行,直到得出結果。


3. 併發,並行

先從定義說起,定義經過我通俗化了,原定義有點難理解。

併發是指一個時間段內,有幾個程式都在同一個CPU上執行,但任意一個時刻點上只有一個程式在處理機上執行。

並行是指一個時間段內,有幾個程式都在幾個CPU上執行,任意一個時刻點上,有多個程式在同時執行,並且多道程式之間互不干擾。

兩者區別如下圖

       

並行是多個程式在多個CPU上同時執行,任意一個時刻可以有很多個程式同時執行,互不干擾。

併發是多個程式在一個CPU上執行,CPU在多個程式之間快速切換,微觀上不是同時執行,任意一個時刻只有一個程式在執行,但巨集觀上看起來就像多個程式同時執行一樣,因為CPU切換速度非常快,時間片是64ms(每64ms切換一次,不同的作業系統有不同的時間),人類的反應速度是100ms,你還沒反應過來,CPU已經切換了好幾個程式了。

切換耗時:執行緒用完了時間片,釋放CPU控制權,引發系統中斷,排程程式根據相關策略選取下一個執行緒來執行,這裡需要一點耗時。

舉個例子吧,並行就是,多個人,有人在掃地,有人在做飯,有人在洗衣服,掃地,做飯,洗衣服都是同時進行的。
併發就是,有一個人,這個人一會兒掃地,一會兒做飯,一會兒洗衣服,他在這3件事中來回做,同一時刻只做一件事,不是同時做的,但最後3件事都可以做完。

  • 時間片大小的選取

時間片取的小,假設是20ms,切換耗時假設是 10ms。
那麼使用者感覺不到多個程式之間會卡,響應很快,因為切換太快了,但是CPU的利用率就低了,20 / (20 + 10) = 66% 只有這麼多,33%都浪費了。

時間片取的大,假設是200ms,切換耗時是 10ms
那麼使用者會覺得程式卡,響應慢,因為要200ms後才輪到我的程式執行,但是CPU利用率就高了,200 / (200 + 10) = 95% 有這麼多被利用的。

所以時間片取太大或者太小都不好,一般在 10 - 100 ms 之間。

  • CPU排程策略

在併發執行中,CPU需要在多個程式之間來回切換,那麼如何切換就有一些策略

3.1 先來先服務 - 時間片輪轉排程
這個很簡單,就是誰先來,就給誰分配時間片執行,缺點是有些緊急的任務要很久才能得到執行。

3.2 優先順序排程
每個執行緒有一個優先順序,CPU每次去拿優先順序高的執行,優先順序低的等等,為了避免優先順序低的等太久,每等一定時間,就給優先順序低的執行緒提高一個級別

3.3 最短作業優先
把執行緒任務量排序,每次拿處理時間短的執行緒執行,就像我去銀行辦業務一樣,我的事情很快就處理完了,所以讓我插隊先辦完,後面時間長的人先等等,時間長的人就很難得到響應了。

3.4 最高響應比優先
用執行緒的等待時間除以服務時間,得到響應比,響應比小的優先執行。這樣不會造成某些任務一直得不到響應。

3.5 多級反饋佇列排程
有多個優先順序不同的佇列,每個佇列裡面有多個等待執行緒。
CPU每次從優先順序高的遍歷到低的,取隊首的執行緒執行,執行完了放回隊尾,優先順序越高,時間片越短,即響應越快,時間片大小就不是固定的了。
每個佇列的內部還是用先來先服務的策略。

原文出處:
作者:ck2016
連結:https://www.jianshu.com/p/2116fff869b6
來源:簡書

處理大併發之一 對非同步非阻塞的理解

    在研究nginx和node.js的時候常會遇到非同步、非阻塞等,之前自己也經常使用epoll,對其同步與阻塞,非同步與非阻塞有了一定的認識,現對參考資料總結下。

    首先討論下使用事件驅動,非同步程式設計的優點:

    充分利用了系統資源,執行程式碼無須阻塞等待某種操作完成,有限的資源可以用於其他的任務。其非常適合於後端的網路服務程式設計。

    在伺服器開發中,併發的請求處理是個大問題,阻塞式的函式會導致資源浪費和時間延遲。通過事件註冊、非同步函式,開發人員可以提高資源的利用率,效能也會改善。其nginx和node.js處理併發都是採用的事件驅動非同步非阻塞模式。其中nginx中處理併發用的是epoll,poll,queue等方式,node.js使用的是libev,它們對大規模的HTTP請求處理的都很好。

阻塞

    《node.js開發指南》是這樣定義的:執行緒在執行中如果遇到(I/O 操作)如磁碟讀寫或網路通訊,通常要耗費較長的時間,這時作業系統會剝奪這個執行緒的 CPU 控制權,使其暫停執行,同時將資源讓給其他的工作執行緒,這種執行緒排程方式稱為 阻塞。當 I/O 操作完畢時,作業系統將這個執行緒的阻塞狀態解除,恢復其對CPU的控制權,令其繼續執行。這種 I/O 模式就是通常的同步式 I/O(Synchronous I/O)或阻塞式 I/O(Blocking I/O)。

非阻塞

    非阻塞是這樣定義的,當執行緒遇到 I/O 操作時,不會以阻塞的方式等待 I/O 操作的完成或資料的返回,而只是將 I/O 請求傳送給作業系統,繼續執行下一條語句。當作業系統完成 I/O 操作時,以事件的形式通知執行 I/O 操作的執行緒,執行緒會在特定時候處理這個事件。

對比阻塞與非阻塞

    阻塞模式下,一個執行緒只能處理一項任務,要想提高吞吐量必須通過多執行緒。

    非阻塞模式下,一個執行緒永遠在執行計算操作,這個執行緒所使用的 CPU 核心利用率永遠是 100%,I/O 以事件的方式通知。

    在阻塞模式下,多執行緒往往能提高系統吞吐量,因為一個執行緒阻塞時還有其他執行緒在工作,多執行緒可以讓 CPU 資源不被阻塞中的執行緒浪費。

    而在非阻塞模式下,執行緒不會被 I/O 阻塞,永遠在利用 CPU。多執行緒帶來的好處僅僅是在多核 CPU 的情況下利用更多的核。

 

    來看看《深入淺出Node.js》對非同步I/O的解釋,在作業系統中,程式執行的空間分為核心空間和使用者空間。我們常常提起的非同步I/O,其實質是使用者空間中的程式不用依賴核心空間中的I/O操作實際完成,即可進行後續任務。

    I/O的阻塞與非阻塞的解釋

    阻塞模式的I/O會造成應用程式等待,直到I/O完成。同時作業系統也支援將I/O操作設定為非阻塞模式,這時應用程式的呼叫將可能在沒有拿到真正資料時就立即返回了,為此應用程式需要多次呼叫才能確認I/O操作完全完成。

 

    I/O的同步與非同步I/O的同步與異步出現在應用程式中。如果做阻塞I/O呼叫,應用程式等待呼叫的完成的過程就是一種同步狀況。相反,I/O為非阻塞模式時,應用程式則是非同步的。

 

    參照《node.js入門經典》中對同步的解釋,同步的程式碼意味著每一次執行一個操作,在一個操作完成之前,程式碼的執行會被阻塞,無法移到下一個操作上。也就是說程式碼的執行會在函式返回前停止。直到函式返回後,程式碼才會繼續執行。

相反,非同步就意味著函式的執行無需等待某個操作的結果就可以繼續執行,其操作的結果會在事件發生時由回撥來處理。

 

非同步I/O優缺點

    使用同步IO,它的優點是可以使程式除錯方便,但是它的缺點也是明顯的,程式的執行過程中如果入到一些耗時的IO操作,程式的執行都要等待該IO的完成,在這個等待的過程中,程式無法充分利用CPU,導致了CPU的閒置,為了充分利用CPU,和IO並行操作,常用的方法有2中:

    (1)多執行緒單程序

    多執行緒的設計之處就是為了在共享的程式空間中,實現並行處理任務,從而達到充分利用CPU的效果。

    多執行緒缺點:

    其一、執行時(執行緒切換)上下文交換的開銷較大,一個執行緒大約需要2M的記憶體空間,佔用資源較大。

    其二、狀態同步(鎖)的問題,它也使得程式的編寫和呼叫複雜化。

    (2)單執行緒多程序

    為了避免多執行緒造成的使用不便問題,有的語言選擇了單執行緒保持呼叫簡單化,採用啟動多程序的方式來達到充分利用CPU和提升總體的並行處理能力。它的缺點在於業務邏輯複雜時(涉及多個I/O呼叫),因為業務邏輯不能分佈到多個程序之間,事務處理時長要遠遠大於多執行緒模式。

非同步I/O與輪詢技術

    當進行非阻塞I/O呼叫時,要讀到完整的資料,應用程式需要進行多次輪詢,才能確保讀取資料完成,以進行下一步的操作。輪詢技術的缺點在於應用程式要主動呼叫,會造成佔用較多CPU時間片,效能較為低下。現存的輪詢技術有以下這些: read、select、poll、epoll、pselect、kqueue 

 

    read是效能最低的一種,它通過重複呼叫來檢查I/O的狀態來完成完整資料讀取。

    select是一種改進方案,通過對檔案描述符上的事件狀態來進行判斷。

    作業系統還提供了poll、epoll等多路複用技術來提高效能。

    輪詢技術滿足了非同步I/O確保獲取完整資料的保證。但是對於應用程式而言,它仍然只能算時一種同步,因為應用程式仍然需要主動去判斷I/O的狀態,依舊花費了很多CPU時間來等待。上一種方法重複呼叫read進行輪詢直到最終成功,使用者程式會佔用較多CPU,效能較為低下。而實際上作業系統提供了select方法來代替這種重複read輪詢進行狀態判斷。select內部通過檢查檔案描述符上的事件狀態來進行判斷資料是否完全讀取。但是對於應用程式而言它仍然只能算是一種同步,因為應用程式仍然需要主動去判斷I/O的狀態,依舊花費了很多CPU時間等待,select也是一種輪詢。

理想的非同步I/O模型

    理想的非同步I/O應該是應用程式發起非同步呼叫,而不需要進行輪詢,進而處理下一個任務,只需在I/O完成後通過訊號或是回撥將資料傳遞給應用程式即可。

 

    暫時就整理這麼多吧,感覺好多看過的東西都忘記了,回頭會寫一篇關於epoll使用的詳細例子,該例子支援2W併發是通過的。哎,今天狀態不好,寫的不好,本打算自己多加點什麼的,結果都是參考別人的,如有錯誤請大家指正,謝謝。

參考資料:

《node.js入門經典》 George Ornbo 著 傅強 陳宗賦 譯 人民郵電出版社

《深入淺出node.js》

《node.js開發指南》 BYVoid 人民郵電出版社

 

原文出處:

曉月天城同步與阻塞,非同步與非阻塞的區別https://www.cnblogs.com/-900401/p/4015048.htm