信號量是一種變量類型,用一個記錄型數據結構表示,有兩個份量:信號量的值和信號量隊列指針
除了賦初值外,信號量僅能經過同步原語PV對其進行操做
s.value爲正時,此值爲封鎖進程前對s信號量可施行的P操做數,即s表明實際可用的物理資源
s.value爲負時,其絕對值爲對信號量s實施P操做而被封鎖並進入信號量s等待隊列的進程數,即登記排列在s信號量隊列之中等待的進程個數
s.value爲0時,無資源且無進程等待
信號量按其取值可分爲二值信號量和通常信號量(計數信號量),記錄型信號量和PV操做定義爲以下數據結構和不可中斷過程:
typedef struct semaphore { int value; struct pcb *list; //信號量隊列指針
} void P(semaphore s){ s.value--; if(s.value<0) sleep(s.list); //若信號量值小於0,執行P操做的進程調用sleep(s.list)阻塞本身,被置成等待信號量s狀態並移入s信號量隊列,轉向進程調度程序
} void V(semaphore s){ s.value++; if(s.value<=0) wakeup(s.list); //若信號量小於等於0,則調用wakeup(s.list)從信號量s隊列中釋放一個等待信號量s的進程並轉換成就緒態,進程則繼續運行
}
二值信號量雖然僅能取值0、1,但它和其它記錄型信號量有同樣的表達能力
注意:(1)P操做與V操做在執行路徑上必須一一對應,有一個P操做就有一個V操做;(2)輸出一張票的操做不該放在臨界區內
int A[m]; Semaphore s = 1; cobegin process Pi { int Xi; Li:按旅客定票要求找到A[j]; P(s); Xi = A[j]; If (Xi>=1) { Xi=Xi-1; A[j]=Xi;V(s); 輸出一張票;} else {V(s); 輸出票已售完;} goto Li; } coend;
只有相同航班的票數纔是相關的臨界資源,因此用一個信號量s處理所有機票會影響進程併發度
可讓每個航班都有本身的臨界區,把信號量改成s[m]
int A[m]; Semaphore s[m]; //每個航班都有本身的臨界區
For (int j=0;j<m;i++) s[j] = 1; cobegin process Pi { int Xi; L1:按旅客定票要求找到A[j]; P(s[j]); Xi = A[j]; If (Xi>=1) { Xi=Xi-1; A[j]=Xi;V(s[j]); 輸出一張票; } else {V(s[j]); 輸出票已售完;} goto L1; } coend;
(1)1生產者1消費者1緩衝區問題
int B; semaphore sput = 1; /* 可使用的空緩衝區數 */ semaphore sget = 0; /* 緩衝區內可使用的產品數 */ process producer { L1: produce a product; P(sput); B = product; V(sget); goto L1; } process consumer { L2: P(sget); product = B; V(sput); consume a product; goto L2; }
(2)1生產者1消費者N緩衝區問題
必須引入放入和取出產品的循環隊列指針putptr指針和getptr指針進行管理,由於只有1生產者1消費者因此它們不須要是共享變量
int B[k]; // 共享緩衝區隊列
semaphore sput = N; //可使用的空緩衝區數
semaphore sget = 0; //緩衝區內可使用的產品數
int putptr = getptr = 0; process producer { L1:produce a product; P(sput); B[putptr] = product; putptr = (putptr + 1) mod k; V(sget); goto L1; } process consumer { L2:P(sget); product = B[getptr]; getptr = (getptr + 1) mod k; V(sput); consume a product; goto L2; }
⭐️(3)N生產者N消費者N緩衝區問題
必須引入新的信號量s1和s2對putptr指針和getptr指針進行管理
int B[k]; semaphore sput = N; /* 可使用的空緩衝區數 */ semaphore sget = 0; /* 緩衝區內可使用的產品數 */
int putptr = getptr = 0; semaphore s1 = s2 = 1; /* 互斥使用putptr、getptr */ process producer_i { L1:produce a product; P(sput); P(s1); B[putptr] = product; putptr = ( putptr + 1 ) mod k; V(s1); V(sget); goto L1; } process consumer_j { L2:P(sget); P(s2); Product = B[getptr]; getptr = ( getptr + 1 ) mod k; V(s2); V(sput); consume a product; goto L2; }
若要求一個消費者取10次,其它消費者才能開始取,則須要再增長一個信號量mutex1並對消費者進程進行改造,循環10次後再V(mutex1)。
這裏經過一個互斥信號量mutex2互斥訪問緩衝區,而不是像前面那樣經過兩個互斥信號量s1和s2分別互斥使用緩衝區存指針putptr、取指針getptr
process consumer_j { P(mutex1); for(int i=0; i<10; i++){ P(full); P(mutex2); 互斥訪問緩衝區; V(mutex2); V(empty); } V(mutex1); }
(4)蘋果橘子問題
父親只放評估、母親只放橙子;兒子只吃橙子、女兒只吃蘋果
同步關係1:有蘋果
同步關係2:有橘子
同步關係3:有空位
Semaphore sp; /* 盤子裏能夠放幾個水果*/ Semaphore sg1; /* 盤子裏有桔子*/ Semaphore sg2; /* 盤子裏有蘋果*/ sp = 1; /* 盤子裏容許放入一個水果*/ sg1 = 0; /* 盤子裏沒有桔子*/ sg2 = 0; /* 盤子裏沒有蘋果*/ process father { L1: 削一個蘋果; P(sp); 把蘋果放入plate; V(sg2); goto L1; } process mother { L2: 剝一個桔子; P(sp); 把桔子放入plate; V(sg1); goto L2; } process son { L3: P(sg1); 從plate中取桔子; V(sp); 吃桔子; goto L3; } process daughter { L4: P(sg2); 從plate中取蘋果; V(sp); 吃蘋果; goto L4; }
(5)吸菸者問題
三個消費者各有一種材料、缺另外兩種材料,供應者每次隨機供應兩種不一樣材料
想法:生產者每次供應兩種不一樣材料,其實能夠理解爲有三種不一樣的生產材料提供方式,分別供三種消費者消費
int random; Semaphore offer1 = offer2 = offer3 = 0; Semaphore finish = 1; process producer(){ while(1){ random = 任意隨機整數; random = random % 3; P(finish); 對應兩種材料放在桌子上; if(random==0) V(offer1); else if(random==1) V(offer2); else V(offer3); } } process consumer_1(){ while(1){ P(offer1); 拿本身缺的那兩種材料; 捲成煙抽掉; V(finish); } }
(6)1生產者2消費者N緩衝區,生產者隨機生成正整數,2個消費者分別取奇數和偶數
思路:這個問題和抽菸者問題很相似,關鍵是要定義緩衝區裏有奇數的互斥信號量odd、緩衝區裏有偶數的互斥信號量even。每次生產者隨機生成一個數以後,判斷奇偶,分別V(odd)和V(even)
Semaphore fork[5]; for (int i = 0; i < 5; i++) fork[i] = 1; cobegin process philsopher_i() { while (true) { think(); P(fork[i]); P(fork[(i+1)%5]); eat(); V(fork[i]); V(fork[(i+1)%5]); } } coend
若是5位哲學家同時拿起他們左手/右手的叉子,將出現死鎖,能夠:
(1)至多容許四個哲學家同時拿叉子
(2)奇數號哲學家先取左邊叉子,再取右邊叉子;偶數號哲學家則相反
(3)每位哲學家取到手邊兩把叉子纔開始吃,不然一把也不取:能夠經過引入互斥信號量mutex每次只容許一個哲學家拿叉子
Semaphore fork[5]; for (int i = 0; i < 5; i++) fork[i] = 1; Semaphore mutex = 1; cobegin process philsopher_i() { while(true){ P(mutex); P(fork[i]); P(fork[(i+1)%5]); V(mutex); eat(); V(fork[i]); V(fork[(i+1)%5]); } } coend
一次只容許一個寫者寫,但能夠N個讀者同時讀。寫者完成寫操做前不容許其它寫者、讀者操做
引入表示是否容許寫的信號量writeblock,至關於任何進程在工做的時候都不容許寫。不過單純引入信號量不能解決此問題,還必須引入計數器readcount對讀進程進行計數,讀以前檢查計數器,若是爲1(本身是惟一的讀進程)才須要P(writeblock),讀以後檢查計數器,若是爲0(當前已無讀進程)則須要V(writeblock)。
mutex是用於對計數器readcount操做的互斥信號量
int readcount = 0; Semaphore writeblock = 1 ; Semaphore mutex = 1; cobegin process read_i() { P(mutex); readcount++; if(readcount==1) P(writeblock); //本身是惟一的讀進程,寫者在寫文件時本身不能開始讀,本身開始讀後不容許寫操做
V(mutex); /*讀文件*/ P(mutex); readcount-—; if(readcount==0) V(writeblock); //本身是惟一的讀進程,讀文件完成後容許寫操做
V(mutex); } process write_j() { P(writeblock); /*寫文件*/ V(writeblock); } coend
此寫法讀者優先,當存在讀者時寫者將被延遲。且只要有一個讀者活躍,隨後而來的讀者都將被容許訪問文件,從而致使寫者長時間等待。
改進方法:增長信號量,確保當一個寫進程聲明想寫時,不容許後面的新讀者訪問共享文件。
這個問題的關鍵是設置一把性別鎖mutex,第一個到的男人要負責搶這把鎖,所以mutex的PV操做必須放在修改mancount的臨界區內。一旦男人搶到了這把鎖,試圖搶這把鎖的女人就會停留在womancount的臨界區內出不來,而每次又只容許一個女人進入womancount的臨界區。
最後一個走的男人要負責把這把鎖釋放掉。
Semaphore mutex; Semaphore mutex_man = mutex_woman= 1; int mancount = womancount =0; Process man(){ P(mutex_man) mancount++; if(mancount==1) P(mutex); V(mutex_man); 洗澡; P(mutex_man); mancount—; if(mancount==0) V(mutex); V(mutex_man) }
南大18年真題過橋問題:汽車只能單向過橋,最多12輛車同時過橋。
引入一個計數器waiting記錄等待理髮的顧客坐的椅子數,初值爲0最大爲N。mutex是用於對計數器waiting操做的互斥信號量
引入信號量customers記錄等候理髮的顧客數,並用於阻塞理髮師進程,初值爲0
引入信號量barbers記錄正在等候顧客的理髮師數,並用於阻塞顧客進程,初值爲0
想法:其實和N生產者1消費者N緩衝區的生產者消費者問題有些相似,可是多了P(barbers)和V(barbers);此外,椅子數改用了int數waiting再用信號量mutex進行互斥。
int waiting = 0; Semaphore customers = 0; Semaphore barbers = 0; Semaphore mutex = 1; cobegin process barbers() { while(true) { P(customers);//判斷是否有顧客,沒有的話理髮師睡眠
P(mutex); waiting--; V(barbers);//理髮師準備爲顧客理髮
V(mutex); cuthair(); //理髮師理髮,不該放在臨界區
} } process customer_i() { P(mutex); if(waiting<N) { waiting++; V(customers);//喚醒理髮師
V(mutex); P(barbers);//若是理髮師忙則等待
get_haircut(); } else V(mutex);//人滿了,顧客離開
} coend