计算机操作系统同步互斥
1 背景
在計(jì)算機(jī)系統(tǒng)里面, 多道程序設(shè)計(jì)是現(xiàn)代操作系統(tǒng)的重要特征, 且并行起到了很大的作用, 所以操作系統(tǒng)抽象出來(lái)了線程/進(jìn)程的概念用來(lái)支持多道程序設(shè)計(jì), 同時(shí), 各個(gè)進(jìn)程之間需要進(jìn)行交互, CPU也需要進(jìn)行調(diào)度來(lái)支持多進(jìn)程. 多進(jìn)程會(huì)涉及到共享資源訪問(wèn)的問(wèn)題, 如果操作系統(tǒng)調(diào)度不當(dāng), 就可能出現(xiàn)饑餓, 死鎖等問(wèn)題. 以下是獨(dú)立進(jìn)程/線程與合作進(jìn)程/線程(進(jìn)程/線程間會(huì)有交互, 并共享資源等)的對(duì)比:
獨(dú)立的進(jìn)程/線程:
- 不和其他進(jìn)程/線程共享資源或狀態(tài)
- 確定性: 輸入狀態(tài)決定結(jié)果
- 可重現(xiàn): 能夠重現(xiàn)起始條件, I/O
- 調(diào)度順序不重要
合作進(jìn)程/線程:
- 在多個(gè)線程中共享狀態(tài)
- 不確定性
- 不可重現(xiàn)
不確定性和不可重現(xiàn)意味著bug可能是間歇性發(fā)生的, 但是計(jì)算機(jī)/設(shè)備需要合作, 其優(yōu)點(diǎn)如下:
- 共享資源
- 一臺(tái)電腦, 多個(gè)用戶(hù)
- 一個(gè)銀行存款余額, 多臺(tái)ATM
- 嵌入式系統(tǒng)(機(jī)器人控制: 手臂與手的協(xié)調(diào))
- 加速
- I/O操作和計(jì)算可以重疊
- 多處理器 - 將程序分成多個(gè)部分并行執(zhí)行
- 優(yōu)化
- 將大程序分解成小程序(以編譯為例, gcc會(huì)調(diào)用cpp, cc1, cc2, as, ld)
- 使系統(tǒng)易于擴(kuò)展
?而我們希望的多進(jìn)程/線程的運(yùn)行方式為:
- 無(wú)論多進(jìn)程/線程的指令序列怎樣交替執(zhí)行, 程序都必須正常工作
- 多線程程序具有不確定性和不可重現(xiàn)的特點(diǎn)
- 不經(jīng)過(guò)專(zhuān)門(mén)設(shè)計(jì), 調(diào)試難度很高
- 不確定性要求并行程序的正確性
- 先思考清楚問(wèn)題, 把程序的行為設(shè)計(jì)清楚
- 切忌急于著手編寫(xiě)代碼, 碰到問(wèn)題再調(diào)試
2 一些概念
競(jìng)態(tài)條件:
?系統(tǒng)缺陷: 結(jié)果依賴(lài)于并發(fā)執(zhí)行或者事件的順序/時(shí)間
- 不確定性
- 不可重現(xiàn)
如何避免競(jìng)態(tài)條件? 可以讓執(zhí)行指令不被打斷
原子操作:
原子操作是指一次不存在任何中斷或者失敗的執(zhí)行:
- 該執(zhí)行成功結(jié)束
- 或者根本沒(méi)有執(zhí)行
- 并且不應(yīng)該發(fā)現(xiàn)任何部分執(zhí)行的狀態(tài)
實(shí)際上操作往往不是原子的
- 有些看上去是原子操作, 實(shí)際上不是
- 連?++這樣的簡(jiǎn)單語(yǔ)句, 實(shí)際上是由3條指令構(gòu)成的
- 有時(shí)候甚至連單條機(jī)器指令都不是原子的
臨界區(qū):
臨界區(qū)是指進(jìn)程中的一段需要訪問(wèn)共享資源并且當(dāng)另一個(gè)進(jìn)程處于相應(yīng)代碼區(qū)域時(shí)便不會(huì)被執(zhí)行的代碼區(qū)域.
互斥:
當(dāng)一個(gè)進(jìn)程處于臨界區(qū)并訪問(wèn)共享資源時(shí), 沒(méi)有其他進(jìn)程會(huì)處于臨界區(qū)并且訪問(wèn)任何相同的共享資源.
死鎖:
兩個(gè)或以上的進(jìn)程, 在相互等待完成特定任務(wù), 而最終沒(méi)法將自身任務(wù)進(jìn)行下去.
饑餓:
一個(gè)可執(zhí)行的進(jìn)程, 被調(diào)度器持續(xù)忽略, 以至于雖然處于可執(zhí)行狀態(tài)卻不被執(zhí)行.
3 臨界區(qū)
3.1 臨界區(qū)中執(zhí)行的屬性
臨界區(qū)中執(zhí)行需要有一些屬性保證臨界區(qū)的順利執(zhí)行:
- 互斥: 同一時(shí)間臨界區(qū)中最多存在一個(gè)線程
- progress: 如果一個(gè)線程想要進(jìn)入臨界區(qū), 那么它最終會(huì)成功(也就是說(shuō)不會(huì)讓一個(gè)想要進(jìn)入臨界區(qū)的線程一直等待, 總會(huì)進(jìn)入的)
- 有限等待: 如果一個(gè)線程 i 處于入口區(qū), 那么在 i 的請(qǐng)求被接受之前, 其他線程進(jìn)入臨界區(qū)的時(shí)間是有限制的(只會(huì)等待有限制的時(shí)間)
- 無(wú)忙等待(可選): 如果一個(gè)進(jìn)程在等待進(jìn)入臨界區(qū), 那么在它可以進(jìn)入之前會(huì)被掛起(就是不要用while循環(huán)一直在檢查是否能進(jìn)入, 會(huì)消耗CPU資源)
3.2 三種針對(duì)臨界區(qū)屬性的保護(hù)方法
3.2.1 禁用硬件中斷
在臨界區(qū)里面進(jìn)程執(zhí)行是屏蔽中斷的: 沒(méi)有中斷, 沒(méi)有上下文切換, 因此沒(méi)有并發(fā).
- 硬件將中斷處理延遲到中斷被啟用之后
- 大多數(shù)現(xiàn)代計(jì)算機(jī)體系結(jié)構(gòu)都提供指令來(lái)完成
進(jìn)入臨界區(qū)
- 禁用中斷
離開(kāi)臨界區(qū)
- 開(kāi)啟中斷
利用中斷來(lái)確保臨界區(qū)順利執(zhí)行的缺點(diǎn):
- 一旦中斷被禁用, 線程就無(wú)法被停止
- 整個(gè)系統(tǒng)都會(huì)為你停下來(lái)
- 可能導(dǎo)致其他線程處于饑餓狀態(tài)
- 要是臨界區(qū)可以任意長(zhǎng)怎么辦
- 無(wú)法限制相應(yīng)中斷所需的時(shí)間(可能存在硬件影響)
- 在多CPU的情況下, 只禁用一個(gè)CPU的中斷無(wú)法解決問(wèn)題?
因此需要小心使用
3.2.2 基于軟件的解決方法
滿(mǎn)足互斥, 但是有時(shí)候不滿(mǎn)足progress.
3.2.3 更高級(jí)的抽象
更高級(jí)的抽象方法是需要一定的基礎(chǔ)的, 這個(gè)基礎(chǔ)就是硬件要提供一些原語(yǔ)支持:
- 比如像中斷禁用, 原子操作指令等
- 大多數(shù)現(xiàn)代體系結(jié)構(gòu)都這樣
有了上面的基礎(chǔ), 操作系統(tǒng)就可以利用這些硬件支持來(lái)提供更高級(jí)的編程抽象來(lái)簡(jiǎn)化多進(jìn)程/多線程的并行編程:
- 比如像鎖, 信號(hào)量
- 這些高級(jí)的用于并行編程的抽象概念是通過(guò)上面說(shuō)的硬件原語(yǔ)構(gòu)建的
下面用鎖來(lái)舉例:
鎖是一個(gè)抽象的數(shù)據(jù)結(jié)構(gòu), 也就是上面說(shuō)的高級(jí)的用于并行編程的抽象概念, 具有以下特點(diǎn):
- 一個(gè)二進(jìn)制狀態(tài)(鎖定/解鎖), 兩種方法
- Lock::Acquire() --- 鎖被釋放前一直等待, 然后得到鎖
- Lock::Release() --- 釋放鎖, 喚醒任何等待的進(jìn)程
如果使用鎖來(lái)處理臨界區(qū)的話, 可以如下:
lock_next_pid->Acquire(); new_pid = next_pid++; lock_next_pid->Release();這樣就保證了臨界區(qū)的順利執(zhí)行.
那么lock的兩個(gè)接口是怎么實(shí)現(xiàn)的呢, 前面說(shuō)過(guò), 是通過(guò)現(xiàn)代計(jì)算機(jī)的體系結(jié)構(gòu)提供的硬件支持, 大索數(shù)現(xiàn)代體系結(jié)構(gòu)都提供特殊的原子操作指令:
- 通過(guò)特殊的內(nèi)存訪問(wèn)電路
- 針對(duì)單處理器和多處理器
比如硬件提供了以下兩種原子操作指令:
1. Test - and - Set測(cè)試和置位
其簡(jiǎn)單實(shí)現(xiàn)如下:
boolean TestAndSet(boolean *target) {boolean rv = *target;*target = TRUE;return rv; }2. 交換
- 交換內(nèi)存中的兩個(gè)值
簡(jiǎn)單實(shí)現(xiàn)如下:
void Exchange(boolean *a, boolean *b) {boolean temp = *a;*a = *b;*b = temp; }有了以上兩種硬件提供的原子操作指令: TestAndSet以及Exchange之后, 我們就可以利用這兩條指令來(lái)設(shè)計(jì)鎖的Acquire和Release接口了:
1. 用TestAndSet實(shí)現(xiàn)
class Lock {int value = 0; }Lock::Acquire(){while(TestAndSet(&value)){} }Lock::Release() {value = 0; }設(shè)計(jì)思路:
- Acquire: 初始化的時(shí)候value為0, 代表沒(méi)有其他進(jìn)程/線程進(jìn)入臨界區(qū), 這時(shí)候Acquire里面調(diào)用TestAndSet返回值是0, 但是value被設(shè)置成了1, 這樣就可以直接退出, 從而進(jìn)入臨界區(qū)執(zhí)行, 這時(shí)候如果其他進(jìn)程調(diào)用這個(gè)lock的Acquire, 這時(shí)候value是1了,?TestAndSet返回值是1, value設(shè)置為1, 這樣就會(huì)一直在while條件里面循環(huán).(忙等)
- Release: 把value置為0, 這樣就會(huì)讓其他調(diào)用Acquire的線程中TestAndSet的返回值置為0從而退出while循環(huán)從而繼續(xù)執(zhí)行
方法評(píng)價(jià):
這種方法是忙等, 會(huì)占用CPU資源. 可以使用在等待過(guò)程中掛起該線程的方式, 并喚醒其他等待進(jìn)程的方式. 但是這種方式需要上下文切換, 如果預(yù)計(jì)等待時(shí)間較長(zhǎng)的話, 可以選擇這種方式.
2. 用Exchange實(shí)現(xiàn)
簡(jiǎn)單實(shí)現(xiàn)如下:
int lock = 0; //設(shè)置一個(gè)全局初始變量 //以下是線程Ti想要進(jìn)入臨界區(qū)的操作 int key; do {key = 1;while (key == 1)exchange(&lock, &key);//然后執(zhí)行臨界區(qū)代碼lock = 0;//執(zhí)行剩余代碼 }設(shè)計(jì)思路:
- 當(dāng)?shù)谝粋€(gè)線程想要進(jìn)入臨界區(qū)之前, 先把key設(shè)置為1, 然后判斷key = 1的話, 就執(zhí)行交換key和lock, 這時(shí)lock被置為1, 因?yàn)槭堑谝粋€(gè)線程, 所以此時(shí)key為0, 退出循環(huán), 執(zhí)行臨界區(qū)代碼.
- 當(dāng)其他線程在第一個(gè)線程執(zhí)行臨界區(qū)代碼過(guò)程中想要進(jìn)入臨界區(qū), 執(zhí)行exchange之后, 因?yàn)閘ock值在有線程在臨界區(qū)執(zhí)行時(shí)都會(huì)是1, 所以key一直都是1, 也就會(huì)一直在while中循環(huán).
- 在臨界區(qū)線程執(zhí)行完畢后, 會(huì)把lock置為0, 讓其他線程進(jìn)入.?
優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn): 適用于單處理器或者共享主存的多處理器中任意數(shù)量的進(jìn)程, 簡(jiǎn)單并且容易證明, 可以用于支持多臨界區(qū).
- 缺點(diǎn): 忙等待消耗處理器時(shí)間, 當(dāng)進(jìn)程離開(kāi)臨界區(qū)并且多個(gè)進(jìn)程在等待的時(shí)候可能導(dǎo)致饑餓. 死鎖: 如果一個(gè)低優(yōu)先級(jí)進(jìn)程擁有臨界區(qū)并且一個(gè)高優(yōu)先級(jí)進(jìn)程也需求, 那么高優(yōu)先級(jí)進(jìn)程會(huì)獲得處理器并等待臨界區(qū), 最終結(jié)果是高優(yōu)先級(jí)進(jìn)程忙等占用CPU, 低優(yōu)先級(jí)進(jìn)程占用臨界區(qū)卻得不到CPU資源進(jìn)而無(wú)法釋放鎖, 導(dǎo)致兩個(gè)進(jìn)程的臨界區(qū)代碼都無(wú)法正常執(zhí)行.
總結(jié)
以上是生活随笔為你收集整理的计算机操作系统同步互斥的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: onenote快捷键_高效飞快地使用on
- 下一篇: 微软2022服务器,微软公开地分享了即