操作系統:經典同步問題



以下羅列一些在多道程序環境下,產生的一系列經典的進程同步問題。

生產者-消費者問題

問題描述

生產者-消費者(producer-consumer)問題是有一群生產者進程在生產產品,並將這些產品提供給消費者進程去消費。在兩者之間設置了一個具有 n 個緩沖區的緩沖池,生產者進程將其所生產的產品放入一個緩沖區中,消費者進程可從一個緩沖區中取走產品去消費。只有緩沖區沒滿時,生產者才能把產品放入緩沖區,否則必須等待。只有緩沖區不空時,消費者才能從中取出產品,否則必須等待。緩沖區是臨界資源,各進程必須互斥地訪問。

可以利用一個數組 buffer 來表示具有 n 個緩沖區的緩沖池,每投入或取出一個產品時,緩沖池 buffer 中暫存產品或或被取走產品的數組單元指針 in 或 out 需要移動,這些用代碼描述如下。

int in  = 0;    //輸入指針
int out = 0;    //輸出指針
item buffer[n];    //緩沖區

由於 buffer 描述的緩沖池是循環隊列結構,因此輸入指針 in 或輸出指針 out 表示成“in = (in + 1) % n” 和 “out = (out + 1) % n”,當 (in + 1) % n = out 時表示緩沖池滿,in = out 則表示緩沖池空。

記錄型信號量解法

可利用互斥信號 mutex 實現諸進程對緩沖池的互斥使用,利用信號量 empty 和 full 分別表示緩沖池中空緩區和滿緩沖區的數量。

semaphore mutex = 1;    //互斥信號量,實現對緩沖區的互斥訪問
semaphore empty = n;    //同步信號量,表示空閑緩沖區的數量
semaphore full  = 0;    //同步信號量,表示非空緩沖區的數量

又假定這些生產者和消費者相互等效,只要緩沖池未滿,生產者可將消息送入緩沖池.只要緩沖池未空,消費者便可從緩沖池中取走一個消息。應注意每個程序中用於實現互斥的 wait(mutex) 和 signal(mutex) 必須成對地出現,其次對資源信號量 empty 和 full 的 wait 和 signal 操作。
對生產者而言,可以用代碼描述如下:

void proceducer(){
    do{
        producer an item nextp;    //生產一個產品
        wait(empty);    //消耗一個空閑緩沖區
        /*實現互斥*/
        wait(mutex);
        buffer[in] = nextp;    //產品放入緩沖區
        in = (in + 1) % n;    //移動輸入指針
        signal(mutex);
        /*實現互斥*/
        signal(full);
    }while(TRUE);
}

對消費者而言,可以用代碼描述如下:

void consumer(){
    do{
        wait(full);    //消耗一個產品
        /*實現互斥*/
        wait(mutex);
        nextc = buffer[out];    //產品拿出緩沖區
        out = (out + 1) % n;    //移動輸出指針
        signal(mutex);
        /*實現互斥*/
        signal(empty);    //增加一個空閑緩沖區
        consumer the item in nextc;    //使用產品
    }while(TRUE);
}

整個生產消費者問題的流程,用代碼描述如下:

void main() {
    cobegin
        proceducer(); 
        consumer();
    coend
}

AND 信號量解法

利用 AND 信號量來解決時,用 Swait(empty,mutex) 來代替 wait(empty) 和 wait(mutex),用 Ssignal(mutex, full) 來代替 signal(mutex) 和 signal(full)。用 Swait(full,mutex)代替 wait(full) 和 wait(mutex),以及用 Ssignal(mutex,empty) 代替 Signal(mutex) 和 Signal(empty)。利用 AND 信號量來解決生產者-消費者問題的代碼描述如下:

int in  = 0;    //輸入指針
int out = 0;    //輸出指針
item buffer[n];    //緩沖區
semaphore mutex = 1;    //互斥信號量,實現對緩沖區的互斥訪問
semaphore empty = n;    //同步信號量,表示空閑緩沖區的數量
semaphore full  = 0;    //同步信號量,表示非空緩沖區的數量

void proceducer(){
    do{
        producer an item nextp;
        Swait(empty, mutex);
        buffer[in] = nextp;
        in = (in + 1) % n;
        Ssignal(mutex, full);
    }while(TRUE);
}

void consumer(){
    do{
        Swait(full, mutex);
        nextc= buffer[out];
        out = (out + 1) % n;
        Ssignal(mutex, empty);
        consumer the item in nextc;
    }while(TRUE);
}

void main() {
    cobegin
        proceducer(); 
        consumer();
    coend
}

管程解法

利用管程來解決生產者-消費者問題時,首先便是為它們建立一個管程,並命名 為procducerconsumer(PC)。用整型變量 count 來表示在緩沖池中已有的產品數目,其中包括兩個過程:

過程 說明
put(x) 生產者利用該過程將自己生產的產品投放到緩沖池中
get(x) 消費者利用該過程從緩沖池中取出一個產品
對於條件變量 notfull 和 notempty,分別有兩個過程 cwait 和 csignal 對它們進行操作:
過程 說明
--- ---
cwait(condition) 當管程被一個進程占用時,其他進程調用該過程時阻塞,並掛在條件 condition 的隊列上
csignal(condition) 喚醒在 cwait 執行后阻塞在條件 condition 隊列上的進程
PC 管程可描述如下:
Monitor procducerconsumer {
    int in  = 0;    //輸入指針
    int out = 0;    //輸出指針
    item buffer[n];    //緩沖區
    condition notfull, notempty;    //條件變量
    int count = 0;    //緩沖池中已有的產品數目

    public void put(item x){
        if(count >= N)    //緩沖池已滿
        {
            cwait(notfull);    //生產者等待
        }
        buffer[in] = x;
        in = (in + 1) % N;
        count++;
        csignal(notempty);
    }

    public void get(item x){
        if (count<= 0)    //緩沖池沒有可用的產品
        {
            cwait(notempty);    //消費者等待
        }
        x = buffer[out];
        out =(out+1) % N;
        count--;
        csignal(notfull);
   }

}PC;

在利用管程解決生產者-消費者問題時,可用代碼描述為:

void producer(){
    item x;
    while(TRUE){
        produce an item in nextp;
        PC.put(x);
    }
}

void consumer(){
    item x;
    while(TRUE){
        PC.get(x);
        consume the item in nextc;
    }
}

void main() {
    cobegin
        proceducer(); 
        consumer();
    coend
}

哲學家進餐問題

問題描述

一張圓桌上坐着 5 名哲學家,每兩個哲學家之間的桌上擺一根筷子,桌子的中間是一碗米飯。哲學家只做思考和進餐兩件事情,哲學家在思考時不影響他人,只有當哲學家飢餓時才試圖拿起左、右兩根筷子(一根一根地拿起)。如果筷子已在他人手上則需等待,飢餓的哲學家只有同時拿起兩根筷子才可以開始進餐,當進餐完畢后,放下筷子繼續思考。

解法

經分析可知,放在桌子上的筷子是臨界資源,在一段時間內只允許一位哲學家使用。為了實現對筷子的互斥使用,可以用一個信號量表示一只筷子,由這五個信號量構成信號量數組。

semaphore chopstick[5] = {1,1,1,1,1};

所有信號量均被初始化為 1,當哲學家飢餓時總是先去拿他左邊的筷子,成功后再去拿他右邊的筷子便可進餐。進餐完畢時先放下他左邊的筷子,然后再放他右邊的筷子。

do{
    wait(chopstick[i]);              //拿起左邊的筷子
    wait(chopstick[(i + 1) % 5]);    //拿起右邊的筷子
    eat
    signal(chopstick[i]);              //放下左邊的筷子
    signal(chopstick[(i + 1) % 5]);    //放下右邊的筷子
    think
}while(TRUE);

除了利用記錄型信號量,也可以使用 AND 型信號量來解決,這樣的寫法更為簡潔。

do{
    Sswait(chopstick[(i + 1) % 5], chopstick[i]);    //拿起筷子
    eat
    Ssignal(chopstick[(i+1)%5],chopstick[i]);    //放下筷子
    think
}while(TRUE);

可能的死鎖

假如五位哲學家同時飢餓而各自拿起左邊的筷子時,就會使五個信號量 chopstick 均為 0,當他們再試圖去拿右邊的筷子時,都將因無筷子可拿而無限期地等待。對於這樣的死鎖問題,可采取以下幾種解決方法:

  1. 至多允許有四位哲學家同時去拿左邊的筷子,最終能保證至少有一位哲學家能夠進餐,並在用畢時能釋放出他用過的兩只筷子;
  2. 僅當哲學家的左、右兩只筷子均可用時,才允許他拿起筷子進餐。
  3. 奇數號哲學家先拿他左邊的筷子,然后再去拿右邊的筷子,而偶數號哲學家則相反。按此規定將是 1、2 號哲學家競爭 1 號筷子,3、4 號哲學家競爭 3 號筷子。即五位哲學家都先競爭奇數號筷子,獲得后再去競爭偶數號筷子,最后總會有一位哲學家能獲得兩只筷子而進餐。

讀者-寫者問題

問題描述

有讀者和寫者兩組並發進程,共享一個文件,當兩個或兩個以上的讀進程同時訪問共享數據時不會產生副作用。但若某個 Reader 進程和其他進程(Reader 進程或 writer 進程)同時訪問共享數據時,則可能導致數據不一致的錯誤。因此要求:

  1. 允許多個 Reader 可以同時對文件執行讀操作;
  2. 只允許一個 writer 往文件中寫信息;
  3. 任一 writer 在完成寫操作之前不允許其他 Reader 或 writer 工作;
  4. writer 執行寫操作前,應讓已有的 Reader 者和 writer 全部退出。

記錄型信號量解法

為實現 Reader 與 Writer 進程間在讀或寫時的互斥,設置一個互斥信號量 Wmutex,再設置一個整型變量 Readcount 表示正在讀的進程數目。又因為 Readcount 是一個可被多個 Reader 進程訪問的臨界資源,因此也應該為它設置一個互斥信號量 rmutex。

semaphore rmutex = 1;    //用於保證對 count 變量的互斥訪問
semaphore wmutex = 1;    //用於實現對文件的互斥訪問,表示當前是否有進程在訪問共享文件
int readcount = 0;    //記錄當前有幾個讀進程在訪問文件

對 reader 而言,可以用代碼描述如下:

void reader(){
    do{
        wait(rmutex);    //reader 進程互斥訪問 readcount
        if(readcount == 0)    //第一個 reader 進程開始讀
        {
            wait(wmutex);    //給共享文件“加鎖”
        }
        readcount++;    //訪問文件的 reader 進程數加 1
        signal(rmutex);
        perform read operation;    //讀文件
        wait(rmutex);    //各個 reader 進程互斥訪問 readcount
        readcount--;    //訪問文件的 reader 進程數減 1
        if(readcount == 0)
        {
            signal(wmutex);    //最后一個 reader 進程“解鎖”
        }
        signal(rmutex);
    }while(TRUE);
}

對 Writer 而言,可以用代碼描述如下:

void writer()
{
    do{
        wait(wmutex);    //寫之前“加鎖”
        perform write operation;
        signal(wmutex);    //寫之后“解鎖”
    }while(TRUE);
}

對於整個讀者-寫者問題過程,可以用代碼描述如下:

void main() {
    cobegin
        reader();
        writer();
    coend
}

信號量集機制解法

此時讀者一寫者問題引入一個限制,最多只允許 RN 個讀者同時讀,為此又引入了一個信號量 L,並賦予其初值為 RN。通過執行 wait(L, 1, 1) 操作來控制讀者的數目,每當有一個讀者進入時,就要先執行 wait(L,1,1) 操作,使 L 的值減 1。當有 RN 個讀者進入讀后,L 便減為 0,第 RN + 1 個讀者要進入讀時,必然會因 wait(L,1,1) 操作失敗而阻塞。

int RN;    //最多允許同時讀取文件的 reader 進程數
semaphore L = RN;    //保證最多只有 RN 個 reader 進程同時讀
semaphore mx = 1;    //標志是否有 writer 進程在操作文件 

void reader(){
    do{
        Swait(L, 1, 1);    //增加一個 reader 進程讀文件
        Swait(mx, 1, 0);    //無 writer 進程寫文件
        perform read operation;
        Ssignal(L, 1);    //減少一個正在讀文件的 reader 進程
    }while(TRUE);
}

void writer(){
    do{
        Swait(mx, 1, 1; L, RN, 0)    //無 reader 或 writer 進程在操作,“加鎖”
        perform write operation;
        Ssignal(mx, 1);    //writer 進程“解鎖”
    }while(TRUE);
}

void main(){
    cobegin
        reader();
        writer();
    coend
}

吸煙者問題

問題描述

假設一個系統有三個抽煙者進程和一個供應者進程,每個抽煙者不停地卷煙並抽掉它,但是要卷起並抽掉一支煙需要有三種材料:煙草、紙和膠水。三個抽煙者中第一個擁有煙草,第二個擁有紙、第三個擁有膠水。供應者進程無限地提供三種材料,供應者每次將兩種材料放桌子上,擁有剩下那種材料的抽煙者卷一根煙並抽掉它,並給供應者進程一個信號告訴完成了,供應者就會放另外兩種材料再桌上,這個過程一直重復。

解法

從事件的角度來分析,吸煙者問題有 4 個同步關系,分別是桌上有組合一時第一個抽煙者取走東西,桌上有組合二時第二個抽煙者取走東西,桌上有組合三時第三個抽煙者取走東西,最后是吸煙者發出完成信號,供應者將下一個組合放到桌上。因此需要設置 4 個信號量,來分別對應 4 個同步關系。

semaphore offerl = 0;    //桌上組合一的數量
semaphore offer2 = 0;    //桌上組合二的數量
semaphore offer3 = 0;    //桌上組合三的數量
semaphore finish = 0;    //抽煙是否完成
int i = 0;    //正在吸煙的吸煙者序號

對於材料提供者而言,可以用代碼描述如下:

void provider(){
    while(1){
        if(i == 0){
            將組合一放桌上;
            wait(offer1);
        } 
        else if(i == l){
            將組合二放桌上;
            wait(offer2);
        } 
        else if(i == 2){
            將組合三放桌上;
            wait(offer3);
        }
        i = (i + 1) % 3;
        signal(finish);
    }
}

對於 3 位吸煙者,可以用代碼描述如下:

void smoker1(){
    while(1){
        signal(offer1);
        從桌上拿走組合一,卷煙抽;
        wait(finish);
    }
}

void smoker2(){
    while(1){
        signal(offer2);
        從桌上拿走組合二,卷煙抽;
        wait(finish);
    }
}

void smoker3(){
    while(1){
        signal(offer3);
        從桌上拿走組合三,卷煙抽;
        wait(finish);
    }
}

參考資料

《計算機操作系統(第四版)》,湯小丹 梁紅兵 哲鳳屏 湯子瀛 編著,西安電子科技大學出版社


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM