操作系統:進程同步三大經典問題


 

日期:2019/4/15

內容:進程同步;生產者與消費者;讀寫者;哲學家進餐;信號量機制。

 

一、生產者與消費者問題

1.1 版本1

  • 代碼

void producer()

{

    while (count == n)

        ;

    buff[in] = produce_item();

    in = (in + 1) % n;

    count++;

}

void consumer()

{

    while (count == 0)

        ;

    item = buff[out];

    print(item);

    out = (out + 1) % n;

    count--;

}

  • 存在問題

    >>兩個while循環一直在"忙等",不符合進程同步的"讓權等待"原則。

    >>對於count變量的訪問沒有保護。(需要加鎖保護)

1.2 版本2:使用信號量

  • 代碼

semaphore empty = n, full = 0;

void producer()

{

    while (true)

    {

        wait(empty);

        buffer[in] = produce_item();

        in = (in + 1) % n;

        signal(full);

    }

}

void consumer()

{

    while (true)

    {

        wait(full);

        item = buffer[out];

        print(item);

        out = (out + 1) % n;

        signal(empty);

    }

}

  • 存在問題

    >>如果有2個producer進程,empty>=2時,同時進入wait(empty)之后的臨界區,對於buff的寫和in的寫產生競爭。

    >>如果有2個consumer進程,full>=2時,同時進入wait(full)之后的臨界區,對於out的寫產生競爭。

1.3 版本3:臨界區加鎖(正確版本)

  • 代碼

semaphore pmutex = 1, cmutex = 1;

semaphore empty = n, full = 0;

void producer()

{

    while (true)

    {

        wait(empty);

        wait(pmutex);

        buff[in] = produce_item();

        in = (in + 1) % n;

        signal(pmutex);

        signal(full);

    }

}

void consumer()

{

    while (true)

    {

        wait(full);

        wait(cmutex);

        item = buff[out];

        print(item);

        out = (out + 1) % n;

        signal(cmutex);

        signal(empty);

    }

}

注:教材對於producer和consumer的臨界區都使用了同一個mutex,表示producer和consumer互斥進入臨界區。但是個人感覺似乎沒必要,因為producer和consumer對於buff的訪問不存在競爭關系,只需要保證多個producer進程之間互斥,多個consumer進程之間互斥即可。

 

二、讀者與寫者問題

對於多個進程訪問同一文件:

  • 寫者:可以讀,也可以寫
  • 讀者:只讀
  • 允許多個讀者同時讀
  • 某一寫者在寫,不允許其他讀寫操作

R

R

1

R

W

0

W

R

0

W

W

0

 

與生產者消費者的區別:

  • 生產者不僅僅是寫進程,還必須調整in指針,但是在讀寫者問題當中每個進程的文件讀寫指針是相互獨立的。
  • 消費者同理。

2.1 版本1

  • 代碼

semaphore mutex = 1;

void writer()

{

    while (true)

    {

        wait(mutex);

        write_operation();

        signal(mutex);

    }

}

void reader()

{

    while (true)

    {

        wait(mutex);

        read_operation();

        signal(mutex);

    }

}

  • 問題

    >>不滿足"同時讀"。

2.2 版本2:增加讀者計數器

  • 代碼

semaphore wmutex = 1;

void writer()

{

    while (true)

    {

        wait(wmutex);

        write_operation();

        signal(wmutex);

    }

}

void reader()

{

    while (true)

    {

        if (reader_count == 0)

            wait(wmutex);

        reader_count++;

 

        read_operation();

 

        reader_count--;

        if (reader_count == 0)

            signal(wmutex);

    }

}

  • 問題

    >>可以允許多個reader進程同時讀,但是對reader_count的訪問存在競爭。

2.3 版本3:給reader_count加鎖

rmutex實際上只用於保護reader_count被正確更新。

  • 代碼

semaphore wmutex = 1, rmutex = 1;

int reader_count = 0;

void writer()

{

    while (true)

    {

        wait(wmutex);

        write_operation();

        signal(wmutex);

    }

}

void reader()

{

    while (true)

    {

        if (reader_count == 0)

            wait(wmutex);

        wait(rmutex);

        reader_count++;

        signal(rmutex);

 

        read_operation();

 

        wait(rmutex);

        reader_count--;

        signal(rmutex);

        if (reader_count == 0)

            signal(wmutex);

    }

}

  • 問題

    >>舉例說明紅色部分代碼問題,reader1和reader2同時執行,同時讀取reader_count均為0,那么(假設)先執行reader1的wait1(wmutex),reader1進入阻塞隊列;后執行reader2的wait2(wmutex),reader進入阻塞隊列。(wait是原語操作,1和2必有先后之分)。但是在reader2執行的時候reader_count的值應為1(但實際是0),這就會使reader2成為僵死進程。

2.4 正確版本1:讀者優先

  • 代碼

semaphore wmutex = 1, rmutex = 1;

int reader_count = 0;

void writer()

{

    while (true)

    {

        wait(wmutex); //保證了WW互斥

        write_operation();

        signal(wmutex);

    }

}

void reader()

{

    while (true)

    {

        wait(rmutex); //保證只能有一個reader訪問reader_count

        if (reader_count == 0)

            wait(wmutex);

        reader_count++;

        signal(rmutex);

 

        read_operation();

 

        wait(rmutex); //保證只能有一個reader訪問reader_count

        reader_count--;

        if (reader_count == 0)

            signal(wmutex);

        signal(rmutex);

    }

}

  • 問題

    >>當讀者進程≥1時,隨后讀者進程直接進入臨界區,這是讀者優先的表征。

    >>寫者餓死問題。

    >>假設有進程{R1, W1, R2, R3, ..., Rn}

    >>>>執行R1,那么wmutex變為0,執行read_operation

    >>>>執行W1,wmutex變為-1,阻塞W1

    >>>>執行R2,wmutex不變,執行read_operation

    >>>>對於若干Ri,均是如此,如果CUP資源不足,Ri會進入就緒隊列

    >>>>那么W1則很長時間無法調度,就算被siganl操作移入就緒隊列也是在隊列尾部,產生寫者餓死問題。

2.5 正確版本2:寫者優先

解決寫者餓死問題:保證一個寫進程想寫時(即使它有可能進入阻塞隊列),不允許新的讀進程訪問臨界區。(注意這並不是為了解決"讀和寫不能同時進行")。上面讀者優先的症結在於寫進程想寫,但是讀進程優先,不斷地進入臨界區,即讀的調度優先級比寫高,從而導致讀者餓死。

  • 代碼

int reader_count = 0, writer_count = 0;

semaphore x = 1, y = 1, z = 1;

semaphore wmutex = 1, rmutex = 1;

void writer()

{

    while (true)

    {

        wait(y);

        if (writer_count == 0)

            wait(rmutex);

        writer_count++;

        signal(y);

 

        wait(wmutex);

        write_operation();

        signal(wmutex);

 

        wait(y);

        writer_count--;

        if (writer_count == 0)

            wait(rmutex);

        signal(y);

    }

}

void reader()

{

    while (true)

    {

        wait(z);

        wait(rmutex);

        wait(x);

        if (reader_count == 0)

            wait(wmutex);

        reader_count++;

        signal(x);

        signal(rmutex);

        signal(z);

 

        read_operation();

 

        wait(x);

        reader_count--;

        if (reader_count == 0)

            wait(wmutex);

        signal(x);

 

    }

}

  • 解析
    • x:控制reader_count的訪問競爭。
    • y:控制writer_count的訪問競爭。
    • wmutex:控制有寫進程在寫的時候,讀進程不能進入臨界區。
    • rmutex:寫進程先把rmutex拿到,保證在寫進程運行時,其他所有讀進程均無法進入臨界區(只能阻塞在rmutex隊列上,見紅色代碼)。
    • z:保證了在rmutex的阻塞隊列上,只有一個讀進程在排隊,其余所有讀進程在等待rmutex之前,在z的隊列上排隊。(嘗試把wait(z)和signal(z)去掉理解一下)如果沒有z,則都在rmutex上排隊。
    • 為什么需要z?在rmutex上不允許長的排隊,否則寫進程不能跳過這個長隊列。
    • 舉例說明:{R1, W1, R2, R3, ..., Rn}
      • 如果有z,reader1先wait1,rmutex=0;然后writer再wait(rmutex),rmutex=-1;后面盡管有再多的reader都在堵塞在z。此時,只需要等待reader1執行signal(rmutex),writer即能夠進入就緒狀態,優先於z中阻塞的reader。
      • 如果無z,writer和所有的reader都進入rmutex排隊,實質上無法保證writer優先於reader。

三、哲學家進餐問題

  • 問題描述

    哲學家需要只吃飯和思考,需要用2把叉子才能吃飯。叉子只能用自己座位兩側的。需要避免死鎖和飢餓。

3.1 版本1

  • 代碼

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

void philosopher(int i)

{

    while (true)

    {

        think();

        wait(fork[i]);

        wait(fork[(i + 1) % 5]);

        eat();

        signal(fork[i]);

        signal(fork[(i + 1) % 5]);

    }

}

 

void main()

{

    for (int i = 0; i < 5; i++)

        create_process(philosopher(i));

}

  • 問題

    >>死鎖。每個人同時拿起自己左邊的叉子,即philosopher[i]拿起fork[i],那么每個人在wait(fork[(i + 1) % 5])上都會阻塞。

3.2 版本2(正確版本)

解決方案:同時只允許4個人進入房間就餐,那么即至少能保證有1人可以拿到2個fork。

  • 代碼

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

semaphore room = 4;

void pholosopher(int i)

{

    think();

    wait(room);

    wait(fork[i]);

    wait(fork[(i + 1) % 5]);

    eat();

    signal(fork[i]);

    signal(fork[(i + 1) % 5]);

    signal(room);

}

void main()

{

    for (int i = 0; i < 5; i++)

        create_process(philosopher(i));

}

  • 解析

    保證不會死鎖和飢餓。(管程解決方案待續)


免責聲明!

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



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