1. 程式人生 > >訊號量(生產者和消費者模型)

訊號量(生產者和消費者模型)

訊號量和管程都是作業系統用於同步提供的兩種方法,我們將結合生產者與消費者模型對此進行學習。

什麼是訊號量?

為了提高系統的併發性,我們引入了多程序,多執行緒,但是這樣子帶來了資源競爭,也就是多個程式同時訪問一個共享資源而引發的一系列問題,因此我們需要協調多執行緒對與共享資源的訪問,在任意時刻保證只能有一個執行緒執行臨界區程式碼。為了實現同步,我們可以利用底層硬體支援,也可以利用高層次的程式設計抽象,訊號量和管程屬於後者,是高層次的一種抽象,如下圖:

這裡寫圖片描述

訊號量的定義:

訊號量(semaphore)是一種抽象資料型別,它是由一個(sem)整型變數(用於表示資源數目)和兩個原子操作P,V組成。
這兩個操作分別是:

P操作:在申請資源時候使用,將sem減1,如果sem小於0,就進入等待,否則直接使用資源即可,注意P操作有可能因為沒有資源進入阻塞

V操作:在釋放資源時候使用,將sem加1,如果sem依舊小於等於0,說明之前有程序在等待使用這個資源,因此需要喚醒一個等待程序

訊號量的實現:

由定義,我們給出訊號量的虛擬碼實現:

class Semaphore
{
    //sem只能由P,V進行原子操作
    int sem;
    WaitQueue q;
    P()
    {
        sem--;
        if (sem < 0) //說明沒有資源
        {
            //將執行緒t放入等待佇列q中
//阻塞 } } Q() { sem++; if (sem <= 0)//說明有其他執行緒在等待使用該資源 { //從等待佇列中移除執行緒 //喚醒 } } }

訊號量的分類和使用:

訊號量可分為兩種:

二進位制訊號量:資源數目0或者1
資源訊號量:資源數目為任意非負值
這兩者等價,基於一個可以實現另一個

訊號量一般用於兩種情況:

互斥訪問:臨界區的互斥訪問控制

我們看一個例子,假設有一個共享資源,程序P0,P1對它進行操作,我們希望P0,P1都是互斥訪問這個共享資源,那麼我們該如何用訊號量實現臨界區的互斥訪問?

我們為此資源設定一個訊號量,初值為1,mutx = New Semaphore(1)

mutex->P();
Critical Section;//臨界區操作
mutex->V();

由於P0,P1訪問順序的不確定性,我們不妨讓P0先訪問(P1同理),P0執行mutex->P(),將資源mutex數目由1減為0(這一步是原子操作不可打斷)

此時,如果P1不行進行訪問,那麼P0會順利執行臨界區操作,如果P1進行訪問,那麼由於P0已經執行了對應的P操作讓sem=0,P1自己在執行P操作過程中,sem減1等於-1,那麼P1程序會阻塞自己,進入等待佇列。

直到P0訪問完臨界區,執行V操作,將mutex加1等於0,去喚醒P1程序,P1才會去訪問共享資源。

通過這樣的一種機制,完成了對於臨界區的互斥訪問。

條件同步:執行緒之間的時間等待

同樣的,我們舉例說明,有兩個程序A,B,他們會執行各自的指令

這裡寫圖片描述

其中,執行緒A必須等執行緒B中X指令執行完才可以執行N指令,比如說X是接受資料,N是處理資料這樣的操作等。。。

為了實現這樣的同步機制,我們條件設定一個訊號量,其初值為0

condition = New Semaphore(0)

這裡寫圖片描述

由於A,B的執行次序不定,我們分類討論。

  1. B先執行:B執行到V操作時候,將condition從0加為1,此時如果切換到A執行,那麼A執行到P操作,發現condition-1為0,可以繼續執行N指令
  2. A先執行:A執行到P操作時候,將condition從0減為-1,由於condition<0,則A會阻塞自己,接下來B執行到V操作,將condition從-1加到0,此時B知道有程序在等待資源,因此它去喚醒A,A因此執行了N指令

通過這樣的機制完成同步等待。

生產者和消費者模型

這裡寫圖片描述

這裡寫圖片描述

在上述圖片中,將mutex訊號量用於完成互斥訪問,full,empty則用於完成問題分析中兩個條件同步,因此我們將一個實際問題轉化為訊號量可以解決的問題

這裡寫圖片描述

如上圖,我們在類中定義了三個訊號量用於完成互斥,同步操作,初始時候,mutex表示資源為1,full表示當前滿緩衝區的個數為0,empty表示當前buffer都為空,(我們設定緩衝取的大小為n,因此full+empty == n)

首先必須保證互斥操作,也就是任意時刻只有一個執行緒能操作緩衝區,要麼是生產者,要麼是消費者,因此我們在Deposit(生產者),Remove(消費者)中分別進行了PV操作。(具體過程和上面互斥訪問一致,不清楚可以往前翻看)

下面我們要來處理緩衝區滿或者空時,條件同步是如何實現的?

對於生產者,如果緩衝區滿,則必須阻塞自己,只有等消費者消費了,緩衝區還有空緩衝區才能夠繼續生產,因此,我們需要同時改寫Deposit(c)和Remove(c)的程式碼:

Deposit(c)
{
    empty->P();//檢查是否還有空緩衝區

    mutex->P();
    Add c;
    mutex->V();
}

Remove(c)
{
    mutex->P();
    Remove c;
    mutex->V();

    empty->V();//消費者讀取一個數據,釋放一個空緩衝區資源
}

同理對於消費者,如果緩衝區為空,則必須阻塞自己,只有等生產者生產了,緩衝區還有東西才能夠繼續消費,因此,我們也需要同時改寫Deposit(c)和Remove(c)的程式碼:

Deposit(c)
{
    empty->P();//檢查是否還有空緩衝區

    mutex->P();
    Add c;
    mutex->V();

    full->V();//生產者生成了資料,需要將滿緩衝區個數加1
}

Remove(c)
{
    full->P();//檢查緩衝區裡面是否還有東西,若緩衝區為空,則阻塞自己

    mutex->P();
    Remove c;
    mutex->V();

    empty->V();//消費者讀取一個數據,釋放一個空緩衝區資源
}

這裡寫圖片描述