基本概念
在 Os 中引入進程后,雖然提高了資源的利用率和系統的吞吐量,但由於進程的異步性,也會給系統造成混亂,尤其是在他們爭用臨界資源時。例如,當多個進程去爭用一台打印機時,有可能使多個進程的輸出結果交織在一起,難於區分;而當多個進程去爭用共享變量、表格、鏈表時,有可能致使數據處理出錯。進程同步的主要任務是對多個相關進程在
執行次序上進行協調,以使並發執行的諸進程之間能有效地共享資源和相互合作,從而使程序的執行具有可再現性。
- 在資源共享的情況下:保證諸進程以互斥的方式訪問臨界資源—必須以互斥方式訪問的共享資源;
- 在相互合作的關系中:進程同步的主要任務是保證相互合作的諸進程在執行次序上協調,(有些教材把這種功能稱做“協調”)。相互合作的進程可能同時存在資源共享的關系。
如何實現進程互斥,需要讓進程以互斥的方式進入各自的臨界區,先執行進入區的代碼。
人為地加一段代碼。
臨界資源
必須以互斥方式訪問的共享資源
counter的例子:在機器語言中實現兩個進程給count加一的操作
register1 = count
register1 = register1 + 1
count = register1
register2 = count
register2 = register2 + 1
count = register2
但是如果是並發執行,可能會出現下面的情況
register1 = count
register2 = count
register1 = register1 + 1
register2 = register2 + 1
count = register1
count = register2
結果就不對了。
可見,counter應該作為臨界資源。多個進程必須對其進行互斥訪問
臨界區
在每個進程中訪問臨界資源的那段代碼稱為臨界區。如果能保證諸進程互斥地進入自己的臨界區,便可實現諸進程對臨界資源的互斥訪問。
每個進程在進入臨界區之前,應先對欲訪問的臨界資源進行檢查,看它是否正被訪問。如果此刻該臨界資源未被訪問,進程便可進入臨界區對該資源進行訪問,並設置它正被訪問的標志;如果此刻該臨界資源正被某進程訪問,則本進程不能進入臨界區。
必須在臨界區前面增加一段用於進行上述檢查的代碼,把這段代碼稱為進入區(entry section)。相應地,在臨界區后面也要加上一段稱為退出區(exit section)的代碼,用於將臨界區正被訪問的標志恢復為未被訪問的標志。
同步機制應遵守的規則
- 空閑讓進。當無進程處於臨界區時,表明臨界資源處於空閑狀態,應允許一個請求進入臨界區的進程立即進入自己的臨界區,以有效地利用臨界資源。
- 忙則等待。當已有進程進入臨界區時,表明臨界資源正在被訪問,因而其它試圖進入臨界區的進程必須等待,以保證對臨界資源的互斥訪問。
- 有限等待。對要求訪問臨界資源的進程,應保證在有限時間內能進入自己的臨界區,以免陷入“死等”狀態。
- 讓權等待。當進程不能進入自己的臨界區時,應立即釋放處理機,以免進程陷入“忙等”狀態。飢餓狀態。
信號量機制
用某種類型的變量來表示資源包括臨界資源的使用狀態
對不同的臨界資源必須定義不同的信號量
整型信號量
跟臨界資源對應的共享變量——信號量,來標記他的可用數量(臨界值為1)來控制進程等待還是繼續運行
整型信號量:整形變量共享(全局變量),用他的值來標記資源使用情況>0說明有可用資源;定義一個用於互斥的整形信號量,初始化為1
/*整型信號量的wait和signal操作
思路:為必須互斥訪問的Cs定義一個互斥信號量mutex,初始值為1。
然后,將Cs放在 wait(mutex)和signal(mutex)之間,當Cs可訪問時,wait(mutex)才能正常結束使進程進入Cs。
這是利用信號量來實現進程互斥
*/
var s:integer; ;s為整型信號量
wait(s){ ;用於申請資源
while s≤0 do no-op;
s=s-1;
}
;cs
signal(s){ //用於釋放資源
s=s+1;
}
wait(s)和 signal(s)是兩個原子操作,因此,它們在執行時是不可中斷的。亦即,當一個進程在修改某信號量時,沒有其他進程可同時對該信號量進行修改。此外,在 wait 操作中,對 s 值的測試和做 s:=s-1 操作時都不可中斷。
信號量不是臨界資源,但具有臨界資源的特點,可以被多個進程互斥訪問。對信號量的操作必須是原子性的。(禁止套娃)
為什么不能直接把臨界區定義為原子操作?
內核中臨界區代碼很短,若直接在執行臨界區代碼前關中斷,效率太低
記錄型信號量
在整型信號量機制中的 wait 操作,只要是信號量 s≤0,就會不斷地測試。因此,該機制並未遵循“讓權等待”的准則,而是使進程處於“忙等”的狀態。沒有釋放處理機,沒有放棄對CPU的使用權。
在信號量機制中,除了需要一個用於代表資源數目的整型變量 value 外,還應增加一個進程鏈表指針 L,用於鏈接上述的所有等待進程。記錄型信號量是由於它采用了記錄型的數據結構而得名的。
#記錄型信號量的數據類型 類似結構體
Type semaphore = record
Value:integer //資源數量
L:list of process //阻塞隊列
end
/*記錄型信號量的wait(s)和signal(s)操作*/
procedure wait(s)
var s:semaphore
begin
s.value=s.value-1;
if s.value<0 then //說明原先的資源數為0
block(s.L)
end
procedure signal(s)
var s:semaphore
begin
s.value=s.value+1; //釋放一個資源
if s.value <= 0 then //如果等待隊列里有阻塞的進程,喚醒一個進程
wakeup(s.L) //說明原先的資源數是小於0的,釋放一個之后還等於0,意味着需要喚醒進程
end
上述代碼如何做到讓權等待:
對它的每次 wait 操作,意味着進程請求一個單位的該類資源,使系統中可供分配的該類資源數減少一個,因此描述為 s.value:=s.value-1;當 s.value<0 時,表示該類資源已分配完畢,因此進程應調用 block 原語,進行自我阻塞,放棄處理機,並插入到信號量鏈表s.L 中。
每次signal操作,如果釋放一個資源,資源數還沒有大於0,說明等待隊列里有阻塞的進程。
經典同步問題
生產者消費者模型
生產者進程生產消息,並將消息提供給消費者進程消費。為使生產者進程和消費者進程能並發執行,在它們之間設置了一個具有n個緩沖區的緩沖池,生產者進程可以將它所生產的消息放入一個緩沖區中,消費者進程可以從一個緩沖區中取得一個消息消費。任意兩個進程必須以互斥的方式訪問公共緩沖池。當緩沖池空時,消費者進程必須等待;當緩沖池裝滿消息時,生產者進程必須等待。
一共有兩個進程,生產者進程和消費者進程,他們之間是互斥的關系。這道題的臨界資源是緩沖區。必須先生產后消費。
信號量:
-
mutex
用於實現對公共緩沖池的互斥,初值為1 -
empty
空緩沖區的個數,n -
full
裝有產品的緩沖區數,0
semaphore mutex=1;
semaphore empty=N;
semaphore full=0
//生產者
Producer:
begin
repeat
produce an item in nextp;
wait(empty);
wait(mutex);//wait操作不能顛倒,如果有了臨界區的訪問權,空緩沖區數又為空,就會出現死鎖
buffer(in):=nextp;
in:=(in+1)mod n
signal(mutex);
signal(full);
until false;
end
//消費者
Consumer:
begin
repeat
wait(full);//申請非空緩沖區
wait(mutex);//申請公共緩沖池的訪問權
nextc:=buffer(out);//取出
out:=(out+1)mod n;
signal(mutex);
signal(empty);
consume item in nextc;//消費數據
until false;
end
這是實現相互合作的一個模板
讀者寫者問題
一個數據文件或記錄,可被多個進程共享,我們把只要求讀該文件的進程稱為“Reader進程”,其他進程則稱為“Writer 進程”。允許多個進程同時讀一個共享對象,因為讀操作不會使數據文件混亂。但不允許一個 Writer 進程和其他 Reader 進程或 Writer 進程同時訪問共享對象,因為這種訪問將會引起混亂。
設置信號量:
readcount
對進入共享區的讀進程計數rmutex
用於對多個進程共享的變量readcount
互斥wmutex
用於實現讀/寫互斥,寫/寫互斥
要實現寫的時候,讀寫互斥比較簡單
writer:
begin
repeat
wait(wmutex);
writing operation
signal(wmutex);
end
讀進程:
read:
begin:
repeat
wait(rmutex);
if readcount == 0 then wait(wmutex);//讓第一個讀進程加鎖
readcount++
signal(rmutex);
reading file
wait(rmutex);
readcount--;
if readcount == 0 then signal(wmutex);//讓最后一個讀進程解鎖
signal(rmutex);
end
多個讀進程要使readcount+1,所以需要將readcount加鎖。
哲學家就餐
如果保證只有四個人拿筷子,就不會出現死鎖
https://blog.csdn.net/speedme/article/details/17597373
repeat
wait(mutex);//拿筷子的人的個數
wait(chopstick[i]);
wait(chopstick[(i+1)mod 5]);
eat;
signal(chopstick[i]);
signal(chopstick[(i+1)mod 5]);
signal(mutex);
think;
until false;
練習題
三個進程\(P_1,P_2,P_3\)互斥使用一個包含\(N(N > 0)\)個單元的緩沖區。\(P_1\)每次用produce()
生成一個正整數並用put()
送入緩沖區某一空單元中;\(P_2\)每次用getodd()
從該緩沖區中取出一個奇數並用countodd()
統計奇數個數;\(P_3\)每次用geteven()
從該緩沖區中取出一個偶數並用counteven()
統計偶數個數。請用信號量機制實現這三個進程的同步與互斥活動,並說明所定義信號量的含義。要求用偽代碼描述。
定義信號量,由於三個都是互斥的:
mutex
三個進程的互斥信號量odd
\(P_1\)和\(P_3\)同步信號量奇數的個數even
\(P_1\)和\(P2\)同步信號量偶數的個數empty
同步信號量空緩沖區的個數
semaphore mutex=1;
semaphore odd=0,even=0;
semaphore empty=N;
main()
cobegin{
Process P1
while(True){
number=produce();
wait(empty);
wait(mutex);
put();
signal(mutex);
if number%2 == 0
signal(even);
else
signal(odd);
}
Process P2
while (true){
wait(odd);
wait(mutex);
getodd();
signal(mutex);
signal(empty);
countodd();
}
Process P3
while (true){
wait(even);
wait(mutex);
geteven();
signal(mutex);
signal(empty);
counteven();
}
}coend