文章目錄
進程的同步與互斥
一、進程互斥和同步的相關概念
進程的異步:各併發進程的執行以各自獨立的、不可預知的速度向前推進。
進程的“併發”需要“共享”的支持,各個併發執行的線程不可避免的需要共享一些系統資源(比如內存,打印機、攝像頭等IO設備)
兩種資源共享方式:
- 互斥共享方式
系統中的某些資源,雖然可以提供給多個進程使用,但是一個時間段內只允許一個進程訪問該資源 - 同時共享方式
系統中的某些資源,允許一個時間段內由多個進程“同時”對它們進行訪問。
一個時間段內只允許一個進程使用的資源叫做臨界資源。對於臨界資源的訪問,必須互斥進行。
do
{
entry section; //進入區,負責檢查是否能進入臨界資源,如果可以進入,上鎖即設置正在訪問臨界資源的標誌。
critical section; //臨界區
exit section; //退出區,解除正在訪問臨界資源的標誌
remainder section; //剩餘區
}
while(true)
臨界區是進程中訪問臨界資源的代碼段。
爲了實現對臨界資源的互斥訪問,同時保證系統整體性能,需要遵循以下原則:
- 1、空閒讓進:臨界區空閒的時候,可以允許一個請求進入臨界區的進程立即進入臨界區。
- 2、忙則等待:當有進程進入臨界區的時候,其他試圖進入臨界區的進程必須等待。
- 3、有限等待:對請求訪問的線程,應保證能在有限的時間內進入臨界區(保證不會飢餓)
- 4、讓權等待:當進入不能進入臨界區的時候,應該立即釋放處理機,防止進程忙等待。
總結
二、進程互斥的軟件實現方法
- 單標誌法
- 雙標誌先檢查
- 雙標誌後檢查
- Peterson 算法
1、單標誌法
算法思想:兩個進程在訪問完臨界區後會把使用臨界區的權限轉交給另外一個進程,也就是說每個進程進入臨界區的權限只能被另一個進程賦予。
算法存在的問題:如果允許進入臨界區的進程是P0,而且P0一直不訪問臨界區,那麼雖然此時臨界區空閒,但是並不允許P1訪問。
單標誌法存在的主要的問題就是違背了“空閒讓進”原則。
2、雙標誌先檢查法
算法思想:設置一個布爾型數組flag[],數組中各個元素用來標記各進程想進入臨界區的意願,比如flag=true,表示P0想進入臨界區,每個進程進入臨界區之前先檢查當前有沒有其他的進程想進入臨界區,如果沒有,則把自身對應的標誌flag[i]設置爲true。之後開始訪問臨界區。
//如果按照152637…順序執行,那麼P0和P1將會同時訪問臨界區。
違背了忙則等待的原則/
3、雙標誌後檢查法(先上鎖,後檢查)
如果按照1526進行執行,則會出現P0和P1都無法進入臨界區。
違背了空閒讓進和有限等待原則
4、Peterson算法
在雙標誌後檢查法中可能出現都想進入臨界區,但是誰都不讓誰,最後誰都無法進入,Peterson想到了一種方法,如果雙方都想進入臨界區,可以讓進程嘗試“孔融讓梨”,主動讓對方使用臨界區。
總結
三、進程互斥的硬件實現方法
1、中斷屏蔽方法
利用“開/關中斷指令”實現(與原語的實現思想相同,即在某進程開始訪問臨界區到訪問結束爲止都不允許被中斷,也就不能發生進程切換,因此也不可能發生兩個同時訪問臨界區的情況。)
關中斷、開中斷
2、TestAndSet指令
簡稱TS指令,有的地方也稱爲TestAndSetLock指令或者TSL指令。
TSL是用硬件來實現的,執行的過程中不允許被中斷,只能一氣呵成,以下是用C語言描述的邏輯。
3、Swap指令
硬件實現的,執行的過程中不允許被中斷,只能一氣呵成。
總結
四、信號量機制(整型信號量、記錄型信號量)
-
在雙標誌先檢查法中,進入區”檢查“、“上鎖”操作無法一氣呵成,從而導致兩個進程有可能同時進入臨界區的問題。
-
所有的解決方案都無法實現“讓權等待”
1、信號量機制
用戶進程可以通過使用操作系統提供的一對原語來對信號量進行操作,從而很方便的實現了進程互斥、進程同步。
信號量其實就是一個變量(可以是一個整數,也可以是一個複雜的記錄型變量),可以用一個信號量來表示系統中某種資源的數量。比如系統中只有一臺打印機,就可以設置一個初值爲1的信號量。
一對原語:wait(S)原語和signal(S)原語,可以把原語理解爲我們自己寫的函數,函數名爲wait和signal,括號裏的信號量S就是函數調用的時候傳入的一個參數。
==wait和signal原語常稱爲P、V操作,因此做題的時候把wait(S)和signal(S)兩個操作分別寫爲P(S)、V(S)。
整型信號量
用一個整數型的變量作爲信號量,用來表示系統中某種資源的數量。
記錄型信號量
總結
2、信號量機制實現進程互斥和同步
信號量機制實現進程互斥
- 1、分析併發進程的關鍵活動,劃定臨界區(如:對臨界資源打印機的訪問就應該在臨界區)
- 2、設置互斥信號量mutex,初始值爲1
- 3、在臨界區之前執行P(mutex)
- 4、在臨界區之後執行V(mutex)
P、V操作必須成對出現
信號量機制實現進程同步(前操作之後執行V,後操作之前執行P)
同步:代碼執行有先後順序,互斥沒先後順序
- 1、分析什麼地方需要實現同步關係
- 2、設置信號量S,初始值爲0
- 3、在“前操作”之後執行V(S)
- 4、早“後操作”之前執行P(S)
總結
五、生產者-消費者問題(包含兩個同步和一個互斥)
生產者消費者問題其實就是互斥和同步組合的綜合問題
從上圖可以看出生產者-消費者進程包含2個同步關係和一個互斥關係:
- 同步:緩衝區滿的時候,生產者要等待消費者取走產品
- 同步:緩衝區空的時候,消費者要等待生產者放入產品
- 互斥:緩衝區是臨界資源,各進程必須互斥訪問。
semaphore mutex = 1; //互斥信號量。實現對緩衝區的互斥訪問
semaphore empty = n; //同步信號量,表示空閒緩衝區的數量
semaphore full = 0; //同步信號量,表示產品的數量,也即非空緩衝區的數量
生產者
producer()
{
while(1)
{
生產一個產品;
P(empty); //消耗一個空閒緩衝區
P(mutex);
把產品放入緩衝區;
V(mutex);
V(full); //增加一個產品
}
}
消費者
consumer()
{
while(1)
{
P(full); //消耗一個產品(非空緩衝區)
P(mutex);
從緩衝區取出一個產品;
V(mutex);
V(empty); //增加一個空閒緩衝區
使用產品;
}
}
一定要注意先同步,後互斥。
六、多生產者-多消費者問題
分析步驟:
- 1、分析關係。找出題目中描述的各個進程,分析它們之間的同步、互斥關係。
- 2、整理思路。根據各進程的操作流程確定P、V操作的大致順序。
- 3、設置信號量。互斥信號量的初值一般爲1,同步信號量的初值要看對應資源的初始值是多少。
不同類別的生產者和不同類別的消費者
例子
桌子上有一個盤子,每次只能向盤子中放入一個水果,爸爸專門向盤子中放蘋果,媽媽專門向盤子中放橘子,兒子專門等喫盤子中的橘子,女兒專門等喫盤子中的蘋果,只有盤子爲空的時候,爸爸媽媽纔可以向盤子中放入一個水果,當盤子中有自己需要的水果的時候,兒子或女兒可以從盤子中取出水果
- 互斥關係:
對緩衝區(盤子)的訪問要互斥進行。 - 同步關係:
1、父親將蘋果放入盤子後,女兒才能取蘋果
2、母親將橘子放入盤子之後,兒子才能取橘子
3、只有盤子爲空的時候,父親或母親才能放入水果
dad:
dad()
{
while(1)
{
準備一個蘋果
P(plate);
P(mutex);
把蘋果放入盤子;
V(mutex);
V(apple);
}
}
mom:
mom()
{
while(1)
{
準備一個橘子
P(plate);
P(mutex);
把橘子放入盤子;
V(mutex);
V(orange);
}
}
daughter:
mom()
{
while(1)
{
P(apple);
P(mutex);
從盤子中取出蘋果;
V(mutex);
V(plate);
喫掉蘋果;
}
}
son:
mom()
{
while(1)
{
P(orange);
P(mutex);
從盤子中取出橘子;
V(mutex);
V(plate);
喫掉橘子;
}
}
總結:在生產者-消費者問題中,如果緩衝區大小爲1,那麼有可能不需要設置互斥信號量就可以實現互斥訪問緩衝區的功能,當然,這不是絕對的,要具體問題具體分析。
建議:在考試中如果來不及仔細分析,可以加上互斥信號量,保證各進程一定會互斥訪問緩衝區。但是需要注意的是,實現互斥的P操作一定要在實現同步的P操作之後,否則可能會引起“死鎖”。
七、吸菸者問題
一個系統有三個吸菸者進程和一個供應者進程,每個吸菸者不停捲菸並抽掉它,卷一支菸需要三種材料:菸草、紙和膠水。三個抽菸者中,第一個擁有菸草、第二個擁有紙、第三個擁有膠水,供應者每次將兩種材料放在桌子上,擁有剩下那種材料的抽菸者卷一根菸並抽掉它,並給供應者進程一個信號告訴完成了,供應這就會放另外兩種材料在桌子上,這個過程一直重複。
組合一:紙+膠水
組合二:菸草+膠水
組合三:菸草+紙
- 同步關係(從時間的角度來分析):
1、桌子上有組合一 ---- 第一個抽菸者取走東西
2、桌子上有組合二 ---- 第二個抽菸者取走東西
3、桌子上有組合三 ---- 第三個抽菸者取走東西
4、發出完成信號-------- 供應者將下一個組合放在桌子上
信號量:
semaphore offer1 = 0; //桌子上組合一的數量
semaphore offer2 = 0; //桌子上組合二的數量
semaphore offer3 = 0; //桌子上組合三的數量
semaphore finish = 0; //抽菸是否完成
int i = 0; //用於實現“三個抽菸者輪流抽菸”
provider:
provider()
{
while(1)
{
if(i==0)
{
將組合一放在桌子上;
V(offer1);
}
else if()
{
將組合二放在桌子上;
V(offer2);
}
else
{
將組合三放在桌子上;
V(offer3);
}
i = (i+1)%3;
P(finish);
}
}
smoke1:
smoke1()
{
while(1)
{
P(offer1);
從桌子上拿走組合一,捲菸,抽掉;
V(finsh);
}
}
smoke2:
smoke2()
{
while(1)
{
P(offer2);
從桌子上拿走組合二,捲菸,抽掉;
V(finsh);
}
}
smoke3:
smoke3()
{
while(1)
{
P(offer3);
從桌子上拿走組合三,捲菸,抽掉;
V(finsh);
}
}
讀者-寫者問題
互斥關係:
- 寫進程-寫進程、寫進程-讀進程
設置一個信號量rw,P(rw)和V(rw)其實就是共享文件的“加鎖”和“解鎖”。可以設置一個整數變量count來記錄當前有幾個讀進程正在訪問文件。
信號量:
semaphore rw = 1;
int count = 0;
writer:
writer()
{
while(1)
{
P(rw); //寫之前“加鎖”
寫文件...
V(rw); //寫之後"解鎖"
}
}
reader:
reader()
{
while(1)
{
//如果這邊同時進入兩個讀進程都通過count==0,會出現第一個讀進程進入,第二個讀進程阻塞的情況
if(count == 0)
P(rw); //第一個讀進程負責“加鎖”
count++;
讀文件;
count--;
if(count == 0)
V(rw);
}
}
出現上面這個原因的問題是因爲count=0的操作和P(rw)不是原子的,所以自然而然會想到加一個互斥變量mutex
改進之後的reader:
reader()
{
while(1)
{
P(mutex); //各讀進程互斥訪問count
if(count == 0)
P(rw); //第一個讀進程負責“加鎖”
count++; //訪問文件的讀進程數+1
V(mutex);
讀文件......
P(mutex); //各讀進程互斥訪問count
count--; //訪問文件的讀進程-1
if(count == 0)
V(rw); //最後一個讀進程負責“解鎖”
V(mutex);
}
}
潛在的問題:
- 只要有讀進程還在讀,寫進程就要一直阻塞等待,可能“餓死”,因此這種算法中,讀進程是優先的,讀進程會用於插入在寫進程前面
再次改進之後的reader:
哲學家進餐問題
一張桌子,5個哲學家,每個人左右各一支筷子,只有拿起自己左右兩個筷子的時候,才能喫飯。喫飯完畢後,放下筷子重新思考。
如果每個人都拿起自己左邊的筷子就會產生死鎖:
- 1、可以對哲學家進餐施加一些限制條件,比如最多允許四個哲學家同時進餐,這樣可以保證至少有一個哲學家是可以拿到左右兩個筷子的
- 2、要求奇數號哲學家拿起左邊的筷子,然後再拿右邊的筷子,而偶數號哲學家剛好相反。
管程
也是用來完成進程的互斥和同步
管程有點類似於類