作業系統總結 - 程序同步、通訊、死鎖(三)
阿新 • • 發佈:2019-01-07
作業系統總結 - 程序同步、通訊、死鎖(三)
- 什麼是程序同步、程序互斥
- 程序互斥的軟體實現方法
- 程序互斥的硬體實現方法
- 訊號量機制(
important
) - 用訊號量實現程序互斥、同步、前驅關係
- 生產者-消費者問題
- 多生產者-多消費者
- 吸菸者問題
- 讀者-寫者問題
- 哲學家進餐問題
- 管程
- 死鎖的概念
- 死鎖的處理策略—預防死鎖
- 死鎖的處理策略—避免死鎖(銀行家演算法)
- 死鎖的處理策略—死鎖的檢測與解除
什麼是程序同步、程序互斥
基本定義:
- 程序具有非同步性的特徵,非同步性是指 : 各併發執行的程序以各自獨立的、不可預知的速度向前推進(之前提到);
- 程序同步:指相互合作去完成相同的任務的程序間,由同步機構對執行次序進行協調。(在多道程式環境下,程序是併發執行的,不同程序之間存在著不同的相互制約關係。);
- 程序互斥:指多個程序在對臨界資源進行訪問的時候,應採用互斥方式;
- 簡單來說,同步:多個程序按一定順序執行;互斥:多個程序在同一時刻只有一個程序能進入臨界區。
程序同步
程序互斥
本節小結:
程序互斥的軟體實現方法
本節小結
程序互斥的硬體實現方法
知識總覽
本節小結
訊號量機制(important
)
知識總覽以及問題引入
兩種訊號量機制:整型(S
是一個整形變數)和記錄型(S
在一個結構體中)
下面看一個栗子的執行過程(重點)
①初始化
②、③ 分別給P1
程序和P2
程序分配資源,使得S.value = 0
此時為P3
、P4
程序服務,但是剩餘資源數為S.value = -2
,所以只能進入等待佇列
所以CPU
接下來只能為P1
、P2
服務,此時服務完之後呼叫 signal
並wake up
在等待佇列中的P3
、P4
(每次空閒一個就喚醒等待佇列中的一個)(下圖只畫出將P2
從佇列中抽取出來,最後P3
的過程也是一樣的)
本節小結
用訊號量實現程序互斥、同步、前驅關係
本節小結
生產者-消費者問題
本節小結
多生產者-多消費者
本節小結
吸菸者問題
讀者-寫者問題
程式碼實現:
Rcount = 0; // 當前有幾個讀程序在訪問檔案
semaphore CountMutex = 1;// 用於保證對count變數的互斥訪問
semaphore WriteMutex = 1; // 用於實現對檔案的互斥訪問(寫操作)
void writer(){
while(true){
sem_wait(WriteMutex);// P
// TO DO write();
sem_post(WriteMutex);// V
}
}
// 讀者優先策略
void reader(){
while(true){
sem_wait(CountMutex); //P
if(Rcount == 0) // 第一個程序負責加鎖
sem_wait(WriteMutex);
Rcount++;
sem_post(CountMutex); //V
// TO DO read();
sem_wait(CountMutex);
Rcount--; // 訪問檔案的讀程序數-1
if(Rcount == 0)
sem_post(WriteMutex); // 最後一個程序負責解鎖
sem_post(CountMutex);
}
}
防止寫程序餓死的方法:
本節小結
哲學家進餐問題
//一種錯誤的解法,考慮到如果所有哲學家同時拿起左手邊的筷子,
//那麼就無法拿起右手邊的筷子,造成死鎖。
#define N 5 // 哲學家個數
void philosopher(int i) // 哲學家編號:0 - 4
{
while(1)
{
think(); // 哲學家在思考
take_fork(i); // 去拿左邊的叉子
take_fork((i + 1) % N); // 去拿右邊的叉子
eat(); // 吃飯
put_fork(i); // 放下左邊的叉子
put_fork((i + 1) % N); // 放下右邊的叉子
}
}
三種方案
第三種方案實現
為了防止死鎖的發生,可以設定兩個條件(臨界資源):
- 必須同時拿起左右兩根筷子;
- 只有在兩個鄰居都沒有進餐的情況下才允許進餐。
實現思路:
//1. 必須有一個數據結構,來描述每個哲學家當前的狀態
#define N 5
#define LEFT i // 左鄰居
#define RIGHT (i + 1) % N // 右鄰居
#define THINKING 0
#define HUNGRY 1
#define EATING 2
typedef int semaphore;
int state[N]; // 跟蹤每個哲學家的狀態
//2. 該狀態是一個臨界資源,對它的訪問應該互斥地進行
semaphore mutex = 1; // 臨界區的互斥,互斥初始值一般是1
//3. 一個哲學家吃飽後,可能要喚醒鄰居,存在著同步關係
semaphore s[N]; // 每個哲學家一個訊號量
void philosopher(int i) {
while(1) {
think(); // 思考
take_two(i); // 拿起兩個筷子
eat();
put_tow(i);
}
}
//拿走兩隻筷子
void take_two(int i) {
P(&mutex); // 進入臨界區
state[i] = HUNGRY; // 我餓了
try(i); // 試圖拿兩隻筷子
V(&mutex); // 退出臨界區
P(&s[i]); // 沒有筷子便阻塞
}
//放回兩隻筷子
void put_tow(i) {
P(&mutex);
state[i] = THINKING;
try(LEFT); // 左邊的人嘗試
try(RIGHT); //右邊的人嘗試
V(&mutex);
}
void try(i) { // 嘗試拿起兩把筷子
if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) {
state[i] = EATING;
V(&s[i]); // 通知第i個人可以吃飯了
}
}
本節小結
- 哲學家問題關鍵在於解決程序死鎖;
- 這些程序之間只存在互斥關係,但是和之前的互斥關係不同的是: 每個程序都需要同時持有兩個臨界資源,因此有死鎖的可能;
管程(高階同步機制)
知識總覽
引入
本節小結
死鎖的概念
知識總覽
- 死鎖: 如果一個程序集合裡面的每個程序都在等待只能由這個集合中的其他一個程序(包括他自身)才能引發的事件,這種情況就是死鎖。
- 互斥:每個資源要麼已經分配給了一個程序,要麼就是可用的。
- 不可剝奪:已經分配給一個程序的資源不能強制性地被搶佔,它只能被佔有它的程序顯式地釋放。
- 請求和保持:已經得到了某個資源的程序可以再請求新的資源。
- 迴圈等待:有兩個或者兩個以上的程序組成一條環路,該環路中的每個程序都在等待下一個程序所佔有的資源。
本節小結
死鎖的處理策略—預防死鎖
知識總覽
本節小結
死鎖的處理策略—避免死鎖(銀行家演算法)
知識總覽
- 注意安全狀態是隻要找到一個安全序列即可。
不會發生死鎖的舉例
可能發生死鎖的情況舉例
實現銀行家演算法
死鎖的處理策略—死鎖的檢測與解除
知識總覽
本節小結