一、背景
獨立的線程
- 不和其他線程共享資源或狀態
- 確定性:輸入狀態決定結果
- 可重現:能夠重現起始條件,I/O
- 調度順序不重要
合作線程
- 在多個線程中共享狀態
- 不確定性
- 不可重現
不確定性和不可重現意味着bug可能是間歇性發生的。
進程/線程:計算機/設備需要合作
- 優點:共享資源、加速、模塊化。
無論多個線程的指令序列怎樣交替執行,程序都必須正常工作
- 多線程程序具有不確定性和不可重現的特點
- 不經過專門設計,調試難度很高 不確定性要求並行程序的正確性
二、基本概念
由於產生異常現象(競態條件Race Condition),引入同步互斥機制,要解決這種不確定性。
1. 系統缺陷:結果依賴於併發執行或者事件的順序/時間
- 不確定性
- 不可重現
2. 怎樣避免競態:讓指令不被打斷——原子操作(Atomic Operation)
-
原子操作:指一次不存在任何中斷或者失敗的執行
- 該執行成功結束
- 或者根本沒有執行
- 並且不應該發現任何部分執行的狀態
-
實際上操作往往不是原子的
- 有些看上去是原子操作,實際不是
- 連x++這樣簡單的語句,實際上是由3條指令造成的
- 有時候甚至連條單條機器指令都不是原子的
-
臨界區(Critical section)
指進程中的一段需要訪問共享資源並且當另一個進程處於相應代碼區域時便不會被執行的代碼區域。簡單來說,就是訪問共享資源的那段代碼就是臨界區。
-
互斥(Mutual exclusion)
當一個進程處於臨界區並訪問共享資源時,沒有其他進程會處於臨界區並且訪問任何相同的共享資源。
-
死鎖(Dead lock)
兩個或以上的進程,在互相等待完成特定任務,而最終沒法將自身任務進行下去。
-
飢餓(Starvation)
一個可執行的進程,被調度器持續忽略,以至於雖然處於可執行狀態卻不被執行。
三、臨界區
1. 在臨界區中執行所擁有的屬性:
- 互斥:同一個時間臨界區中最多存在一個線程
- 前進(Progress):如果一個線程想要進入臨界區,那麼它最終會成功,不會一直的死等。
- 有限等待:如果一個線程i處於入口區,那麼在i的請求被接受之前,其他線程進入臨界區的時間是有限制的。如果是無限等待,就會出現飢餓狀態,是Progress前進狀態的一種進一步補充。
- 忙等(可選屬性):如果一個進程在等待進入臨界區,那麼在它可以進入之前會被掛起。
2. 基於這些屬性,設計一些方法對臨界區進行保護:
方法一:禁用硬件中斷
1. 基本實現
沒有中斷,也就是沒有了上下文切換,因此沒有併發。
-
硬件將中斷處理延遲到中斷被啓用之後
-
大多數現代計算機體系結構都提供指令來完成
進入臨界區時:禁用中斷
離開臨界區時:開啓中斷。
2. 缺點
-
一旦中斷被禁用,線程就無法被停止
- 整個系統都會爲你停下來
- 可能導致其他線程處於飢餓狀態
-
要是臨界區可以任意長
無法限制響應中斷所需的時間(可能存在硬件影響)
-
注意:
執行這種屏蔽中斷的指令,只是把自身的響應中斷的能力屏蔽了,並不意味着也將其他cpu的響應中斷能力屏蔽,所以其實其他的cpu還是可以繼續產生中斷,所以在多cpu的情況下是無法解決互斥問題的。
方法二:基於軟件的解決方法
某一個進程,它想進入臨界區,其有一個順序(次序),根據這個次序決定誰會進入這個臨界區。
方法三:更高級的抽象(基於硬件原子操作的指令)
-
優點
- 簡單並且容易證明
- 適用於單處理器或者共享主存的多處理器中任意數量的進程
- 可以很容易拓展n個進程,可以用於支持多臨界區
- 開銷比較小
-
缺點
- 忙等待消耗處理器時間
- 進程離開臨界區並且多個進程在等待的時候可能導致飢餓現象
- 出現死鎖的情況(例子:如果一個低優先級的進程擁有臨界區並且一個高優先級進程也需求,那麼高優先級進程會獲得處理器並且等待臨界區 — 需要用優先級反轉的方式進行處理)
四、總結
1、鎖是更高級的編程抽象
- 互斥可以使用鎖來實現
- 通常需要一定等級的硬件支持
2、常用的三種實現方法
- 禁用中斷(僅限於單處理器)
- 軟件方法(複雜)
- 原子操作指令(但處理器或多處理器均可)—更常用
3、可選的實現內容
- 有忙等待
- 無忙等待