多進程并發運行,導致多個進程間有資源共享,比如CPU、內存,因此存在不確定性和不可重現,可能導致多次運行結果不一致。因此操作系統需要利用同步機制在并發執行的同時,保證一些操作是原子操作。
- 互斥是指一個進程占用了某個資源,其他進程都不能使用該資源;
- 死鎖是指多個進程各占有了一部分資源,形成了循環等待;
- 饑餓是指其他進程輪流占用資源,一個進程一直得不到資源。
臨界區
為解決進程間同步導致的這些問題,提出了一些方案。臨界區是指進程中訪問臨界資源的一段需要互斥執行的代碼。進入臨界區之前需要判斷能否進入,進入時需要改變標志阻止其他進程進入,進入的進程執行完成后退出時修改標志。
臨界區訪問規則:
- 空閑則入,沒有進程進入時可以進城;
- 忙則等待,有進程在臨界區時其他進程均不可進入;
- 有限等待,等待進入臨界區的進程不能無限制等待;
- 讓權等待,不能進入的進程應釋放CPU進入阻塞狀態。
臨界區實現方式:
- 禁用中斷,進入臨界區后不響應中端;
- 軟件方式,共享變量協調;
- 借用操作系統提供高級的抽象方法。
顯然第三種方式是可取的,其他兩種都有明顯的缺陷。下面對第三種方式進行介紹。
方法一:禁用中斷
缺點:
- 禁用中斷后,進程無法被停止。整個系統都會為此停下來,可能導致其他進程處于饑餓狀態
- 臨界區可能很長,無法確定響應中斷所需的時間(可能存在硬件影響)
方法二:軟件中斷
???????
缺點:
- 需要兩個進程間的共享數據項
- 需要忙等待,浪費CPU時間
方法三:原子操作指令
更高級的抽象方法實際是借助硬件的同步原語來實現的。
- 硬件提供了一些同步原語,包括中斷禁用、原子操作指令等,從硬件上保證多個操作的原子性。
- 操作系統提供更高級的編程抽象來簡化進程同,。實現方式有鎖、信號量。
鎖
鎖是一個抽象的數據結構,由一個二進制變量(鎖定/解鎖)和兩個操作原語(鎖的請求和鎖的釋放)組成。二進制變量用于標志鎖的狀態,鎖的請求使鎖在被釋放前一直等待,并且使進程得到鎖,這兩者是原子的,釋放鎖時喚醒其他等待鎖的進程。
內部基于CPU體系結構提供的一些特殊的原子操作指令,這些指令把若干個指令合并為一次原子操作,保證不會出現部分執行的狀態。這些原子操作指令包括測試和置位指令(Test-and-Set,即TS指令,返回內存地址中的值,并將其置為1)、交換指令(交換內存中的兩個值)。
基于TS指令可以實現如下圖所示的自旋鎖,自旋鎖的缺點是線程等待時消耗CPU時間。
??????
針對自旋鎖的問題,出現了無忙等待鎖,當鎖已經被占用時,將自身線程放入等待隊列,并調用調度程序。當其他線程釋放鎖時會將所有等待中的線程重新喚醒。