併發編程01-面試中被問到併發基礎知識答不上來?



由於計算機中 CPU 的技術問題,在發展到一定程度的時候越來越難取得重大突破(比如從60分考到90要比從99分考到100分容易)。但是對於計算的需求量一直在增加,既然單核心 CPU 的工作能力不足以支持如此大量的運算任務,那麼製造廠商就開始做橫向的擴展,給一臺計算機安裝多核心 CPU。在同一個時刻每個核心都可以執行運算。這樣提高了計算機的整體性能和工作效率,但是也引入了併發問題。


進程交互有關於並行程序能夠藉此相互通信的機制。最常見的交互形式是共享內存和消息傳遞。

共享內存 (shared memory)

共享內存在硬件術語中指在多處理器的計算機系統中,可以被不CPU 訪問的大容量內存。共享內存在軟件術語中指的是可以被多個進程存取的內存,一個進程是一段程序的單個運行實例。在這種情況下,共享內存被用作進程間的通訊。

共享內存是進程間傳遞數據的高效方式。在共享內存模型中,並行進程共享它們可以異步讀寫的全局地址空間。異步併發訪問可能導致競態條件,和用來避免它們的機制比如:鎖、信號量、監視器。常規的多核處理器直接支持共享內存,很多編程語言和庫在設計上利用了它。

消息傳遞 (message passing)
在消息傳遞模型中,並行進程通過消息傳遞相互交換數據,這種通信可以是異步的,消息可以在接收者準備好之前發出。或是同步的,消息發出前接收者必須做好準備。



同步 synchronization
在計算機科學中同步是指兩個不同但有聯繫的概念:
1. 進程同步:指的是多個進程在特定點匯合 (join up) 或者握手使得達成協議或者使得操作序列有序。
2. 數據同步:指的是一個數據的多分拷貝一致以維護完整性。

臨界區 critical section

多個線程或進程要執行同一個特定的不可重入的程序代碼塊(稱爲臨界區,英文 critical section)。



競態條件
競爭冒險(race hazard)又名競態條件、競爭條件(race condition),它旨在描述一個系統或者進程的輸出依賴於不受控制的事件出現的順序或者出現的時機。此詞源自於兩個信號試着彼此競爭,來影響誰先輸出。舉例來說,如果計算機中的兩個進程同時試圖修改該一個共享內存的內容,在沒有併發控制的情況下,最後結果依賴於兩個進程的執行順序和寫入時機。而如果發生了併發訪問衝突,最後的結果是不正確的。

Monitors
管程也叫監控器,是一種程序結構。結構內的多個子程序(對象或模塊)形成的多個工作線程互斥訪問共享資源。這些共享資源一般是硬件或一些變量。管程實現了在一個時間點,最多隻能有一個線程在執行管程的某個子程序。與哪些通過修改該數據結構實現互斥訪問的併發程序設計相比,管程很大程度上簡化了程序設計。一個管程包括:
1. 多個彼此可以交互並共享資源的線程。
2. 多個與資源使用有關的變量。
3. 一個互斥鎖。
4. 一個用來避免競態條件的不變量。

一個管程的程序在運行一個線程前會先獲取互斥鎖,直到完成線程或是線程等待某個條件被滿足纔會放棄互斥鎖。若每個執行中的線程在放棄互斥鎖之前都能保持不變量的成立,則所有的線程皆不會導致競態條件成立。當一個線程執行管程中的一個子程序時,稱爲佔用(occupy)該管程。管程的實現確保了在一個時間點,最多隻有一個線程佔用了該管程。這是管程的互斥訪問性質。當線程要調用一個定義在管程中的子程序時,必須等到已經沒有其他線程在執行管程中的某個子程序。在管程的簡單實現中,編譯器爲每個管程對象自動加入了一把私有的互斥鎖。該互斥鎖初始狀態爲解鎖,在管程的每個公共子程序的入口給該互斥鎖枷鎖,在管程的每個公共子程序的出口給該互斥鎖解鎖。

條件變量 (Condition Variable)
對於許多應用場合,互操作是不夠用的。線程可能需要等待某個條件P爲真,才能繼續執行。解決辦法是條件變量(Condition Variable)。概念上一個條件變量就是一個線程隊列(queue),其中的線程正等待某個條件變爲真。每個條件變量 c 都關聯着一個斷言 Pc .當一個線程等待一個條件變量,該線程不算佔用了該監控器,因而其他線程也可以進入到該監控器執行,改變監控器的狀態,通知條件變量c其關聯的斷言 Pc 在當前狀態下爲真。

因此對條件變量存在兩種主要操作:
wait c 被一個線程調用,以等待斷言 Pc 被滿足後該線程可恢復執行。線程掛在該條件變量上
等待時,不被認爲是佔用了監控器。
signal c (有時也寫作 notify c) 被一個線程調用,以指出斷言 Pc 現在爲真。
當一個通知(signal)發給了一個有線程處於等待中的條件變量,則有至少兩個線程將要佔用該
監控器:
1. 發出通知的線程。
2. 等待條件變量斷言的某個線程。

條件變量的兩種實現:
1.阻塞式條件變量 (Blocking Condition Variable) ,把優先級給了被通知線程。發出通知(signaling) 的線程必須等待被通知(signaled)的線程放棄佔用監控器。這種方式也被稱爲通知且急迫等待(signal-and-urgent-wait)管程。

2.非阻塞式條件變量 (Nonblocking Condition Variable) , 把優先級給了發出通知的線程。也被稱爲通知且繼續(signal-and-continue)條件變量。發出通知的線程不會失去監視器的佔用權。被通知的線程將會被移入到一個準備爭搶監視器的隊列。非阻塞式條件變量經常把 signal 操作稱作 notify ,也常用 notify all 操作把該條件變量關聯的隊列上的所有線程移入準備爭搶監視器的隊列。

Spurious wakeup
假喚醒是 POSIX Threads 與 Windows API 使用條件變量時可能發生的複雜情形。一個掛在條件變量上的線程被 signalted ,正在等待條件仍有可能是不成立的。假喚醒指的是即使沒有線程 signalted 該條件變量,掛在該條件變量上的線程卻被喚醒。因此,應該用 while 循環包圍條件變量等待操作。

stolen wakeups
被偷走的喚醒是 POSIX Threads 與 Windows API使用條件變量時,線程調用 g_cond_signal 時,另一個線程已經獲取了 mutex 使得期望的條件不再滿足,因此被喚醒的線程面臨着條件不成立。因此應該用 while 循環包圍條件變量等待操作。

互斥鎖(Mutual exclusion,縮寫Mutex)
互斥鎖是一種用於多線程語言編程中,防止兩條線程同時對同一公共資源(比如全局共享變量)進行讀寫機制。該目的通過將代碼切分成一個一個的臨界區域(critical section)達成。臨界區域指的是一塊對公共資源進行訪問的代碼,並不是一種機制或算法。一個程序、進程、線程可以擁有多個臨界區域,但是並不一定會應用互斥鎖。

需求:
1.不準永遠耽擱一個要求進入臨界區域的線程,造成死鎖或是飢餓發生。
2.若沒有任何線程處於臨界區域時,任何要求進入臨界區域的線程必須立刻得到允許。
3.不能對線程的相對速度與處理器的數目做任何假設。
4.線程只能在臨界區域內停留有限的時間。
5.任何時間只允許一個線程在臨界區運行。
6.在臨界區通知的線程,不準影響其他線程運行。

亂序執行
計算機工程領域,亂序執行 (錯序執行 out-of-order-execution ,簡稱 OoOE 或 OOE)是一種應用在高性能微處理器中來利用指令週期以避免特定類型的延遲消耗範式。在這種範式中,處理器在一個由輸入數據可用性所決定的順序中執行指令,而不是由程序的原視數據所決定。在這種方式下,可以避免因爲獲取嚇一跳程序指令所引發的處理器等待,取而代之的處理下一條可以立即執行的指令。

CAS
比較並交換 (compare and swap , CAS)是原子操作的一種。可用於在多線程編程中實現不被打斷的數據交換操作 從而避免在多線程同時修改某一個數據時由於執行順序不確定性以及中斷的不可預知性產生的數據不一致的問題。該操作通過將內存中的數據替換爲新的值。CAS 操作基本 CPU 提供的原子操作指令實現。對於 x86 處理器,可通過在彙編指令前綴增加 LOCK 前綴來鎖定系統總線,使系統總線在彙編指令執行時無法訪問相應的內存地址。而各個編譯器根據這個特點實現了各自的原子操作函數。比如,C語言 C11 的頭文件 。由 GNU 提供了對應的 _sync 系列函數完成原子操作。C++ 11 , STL 提供了 atomic 系列函數。Java ,sun.misc.Unsafe 提供了 compareAndSwap 系列函數。

自旋鎖
自旋鎖是計算機科學用於多線程同步的一種鎖,線程反覆檢查鎖變量是否可用。由於線程在這個過程中一直保持執行,因此是一種忙等待。一旦獲取了自旋鎖,線程會一直保持該鎖,直到顯示釋放自旋鎖。自旋鎖避免了進程上下文的調度開銷,因此對於線程只會阻塞很短時間的場合是有效的。因此OS的實現在很多地方往往使用自旋鎖。顯然,單核 CPU 不適合使用自旋鎖,這裏的單核指的是單核單線程的 CPU 。因爲在同一時間只有一個線程是出於運行狀態,假設運行線程 A 發現無法獲取鎖,只能等待解鎖,但因爲 A 自身不掛起,所以那個持有鎖的線程 B 沒有辦法進入運行狀態,只能等到 OS 分配給 A 的時間片用完,纔能有機會被調度。這種情況下使用自旋鎖的代價很高。獲取、釋放自旋鎖,實際上是讀寫自旋鎖的存儲內存或寄存器。因此這種讀寫操作必須是原子的。通常用 test-and-set 等原子操作實現。

死鎖 deadlock
P1 , P2 兩個 process 都需要資源才能繼續運行。P1 擁有資源 R2 還需要額外資源 R1 才能運行。P2 擁有資源 R1 還需要額外資源 R2 才能運行。兩邊都在互相等待對方放棄自己持有的資源而沒有一個可以執行。

當兩個以上的計算單元,雙方都在等待對方停止運行,以獲取系統資源,但是沒有一方提前退出時,就稱爲死鎖。在多任務操作系統中,當一個或者多個進程等待系統資源,而資源又被進程本身或其他進程佔用時,就形成了死鎖。如果系統中只有一個進程,當然不會出現死鎖。如果每個進程僅需求一種系統資源,也不會產生死鎖。死鎖的四個條件:
1.禁止搶佔(no preemption):系統資源不能被強制從一個進程中退出。
2.持有和等待(hold and wait):一個進程可以在等待時持有系統資源。
3.互斥(mutual exclusion):資源只能同時分配給一個行程,無法多個行程共享。
4.循環等待(circular waiting):一系列進程互相持有其他進程所需要的資源。
死鎖只有在四個條件同時滿足時發生,預防死鎖必須至少破壞其中一項。

活鎖 livelock
活鎖與死鎖類似,死鎖是進程都在等待對方先釋放資源。活鎖則是進程彼此釋放資源又同時佔用對方釋放的資源,當此情況持續發生時,儘管資源的狀態不斷改變,但是每個進程都無法獲取所需要的資源,使得事情沒有任何進展。舉個例子:
死鎖:兩個人在同一條道路上互不相讓,都在等待對方先讓開。活鎖:兩個人在同一條道路上,互相讓路,但是在讓路的時候都恰好站到了同一側,再次讓開,又站到了同一側。同樣的狀況不斷的重複下去導致雙方都無法通過。


相關內容回顧

《帶你瞭解緩存一致性協議 MESI》

講解 CPU 緩存一致性原理


《內存屏障究竟是個什麼鬼?》

內存屏障原理...


《每日閱讀之計算機系統總線》

理解系統總線是如何工作的...


《走進 Java Volatile 關鍵字》

Java Volatile 關鍵字舉例串聯往期內容知識...








本文分享自微信公衆號 - 黑帽子技術(SNJYYNJY2020)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章