PV 操作 - 讀者寫者問題

信號量

信號量的英文叫做 semaphore,是一個用於在進程間傳遞的特殊便量,通常定義成一個結構體,其中包含一個整形變量,以及一個隊列,如下:

struct semaphore {
    
    
    int count;
    Queue queue; // 存放進程
}
  • semaphore.count
    信號量的值 (count) 與相應資源的使用情況有關。當 count > 0 時,表示當前可用資源的數量,當 count < 0 時,其絕對值就表示當前等待使用資源的進程數。這邊要注意,semaphore.count 只能由 PV 操作來改變!

  • semaphore.queue
    semaphore.queue 的作用在於,存放那些被阻塞的進程(等待被喚醒的進程)。就是說,假設當前沒有可用資源了 (semaphore.count <= 0),那麼這時如果又有進程想要使用資源,這時該進程應該進入阻塞狀態,我們就將該進程加入到 semaphore.count 中。
    而當今天某些資源被釋放了(semaphore.count > 0),那這時我們就要喚醒最早開始等待的那個進程,也就是 semaphore.count 的隊列開頭。因此這邊採用先進先出的隊列是比較合適的。

PV 操作

一般來說,S >= 0,S 表示可用資源數量。執行一次 P 操作意味著請求分配一個單位資源,因此 S–,當 S <= 0 表示已經沒有可用資源了,這時請求者就必須等待別的進程釋放該資源,它才能繼續運行,否則就進入阻塞狀態。
而執行一次 V 操作意味著釋放一個單位資源,因此 S++,而當執行完一次 V 操作後,如果此時 S < 0,就表示說當前還有 |S| 個正在等待的阻塞態進程,因此要喚醒一個進程,使之運行下去。

在往下看 PV 操作的實現之前,我們先假設,當前調用 PV 操作的進程為 p_proc_ready,然後對一個進程而言,有兩種狀態(pv_state),分別為阻塞態 PV_PROCESS_BLOCK 以及就緒態 PV_PROCESS_READY

並且,我們還要加上一個 schedule() 方法,目的是在執行完 P 操作後,因為當前進程進入了阻塞態,因此我們要找到下一個要執行的進程並把控制權交給它,簡單來說,schedule() 就是負責重新調度。

P 操作

P 操作意味請求分配一個資源,也稱為 wait() 操作,使 S–,若 S < 0,進程進入阻塞態,放入信號量的等待隊列。

P(semaphore S) {
    
    
    S.count--;
    if(S.count < 0) {
    
    
        p_proc_ready->pv_state = PV_PROCESS_BLOCK;
        S.queue.add(p_proc_ready);
        schedule();
    }
}

V 操作

V 操作意味釋放一個單位資源,也可以稱為 signal() 操作,使 S++,若 S <= 0,要喚醒一個進程,使之繼續執行。

V(semaphore S) {
    
    
    S.count++;
    if(S.count <= 0) {
    
    
        // 喚醒隊列中第一個阻塞進程
        // get() 操作返回出隊元素
        S.queue.get()->pv_state = PV_PROCESS_READY;
    }
}

讀者-寫者問題

讀者-寫者問題可以分為兩種策略,以下就來分別做介紹以及代碼實現。不過在介紹這兩種策略之前,先要有一些基礎知識。

在讀者-寫者問題中,有一區是進程之間共享的數據區,而進程分為讀者進程和寫者進程。讀者進程當然是負責從這個共享區讀取數據,而寫者進程則是向該共享區寫入數據。並且,有一些規則無論是在哪種策略下,都必須遵守:

  1. 同時可以有多個讀者讀共享區
  2. 同時只能有至多一個寫者寫共享區
  3. 如果當前有寫者在寫共享區,那麼就不能有任何讀者在讀共享區

優先的概念

無論是哪種策略,在沒有進程占用臨界區時,讀者與寫者的競爭都是公都是公平的,所謂的不公平(優先)是在讀者優先和寫者優先中,優先方只要佔有臨界區,那麼之後所有優先方的進程就都有了臨界區的主導權。
除非沒有優先方進程提出要求,否則始終是優先方進程佔有臨界區,反觀,即使非優先方在某次佔有了臨界區,那麼釋放過後,回到沒有進程佔有臨界區的情況時,非優先方又要和優先方公平競爭。所謂「優先」的概念其實可以理解為優先方在佔有臨界區後便可以對臨界區進行 “壟斷”。

讀者優先

讀者優先策略的規則如下:

即使寫者發出了請求寫的信號,但是只要還有讀者在讀取內容,就還允許其他讀者繼續讀取內容(體現了優先方始終擁有進入臨界區的主導權),直到所有讀者結束讀取,才真正開始寫。

  1. 有讀者在讀後面來的讀者可以直接進入臨界區,而已經在等待的寫者繼續等待直到沒有任何一個讀者時。
  2. 讀者之間不互斥,寫者之間互斥,只能一個寫,可以多個讀。
  3. 讀者寫者之間互斥,有寫者寫則不能有讀者讀。

讀者優先解決方案:

  • wrt為互斥信號量,其 count == 1 表示可用資源只有一個,相當於只有一個共享的文件。
  • readCount相當於一個計數器,紀錄讀者數,初值為 0。當每個讀者進程來,就要 readCount++,而當 readCount == 1 就表示第一個讀者來了,需要搶佔共享的資源,否則表示已經有其他讀者進程在讀數據了。當每走一個讀者進程,就要 readCount--,當 readCount == 0,就表示走的已經是最後一個讀者進程了,所以需要釋放共享的資源,否則表示還有其他讀者進程還在讀。
  • readCount 是多個讀者進程共享的變量,應該要在臨界區中,用互斥信號量 mutex1 控制,mutex1.count
    初值為 1。
int readCount = 0;
semaphore mutex1, wrt;
mutex1.count = 1;
wrt.count = 1;

// 讀者進程結構
reader_first_reader {
    
    
    P(mutex1);
        readCount++;
        if(readCount == 1) {
    
    
            P(wrt);
        }
    V(mutex1);

    /* reading operation */

    P(mutex1);
        readCount--;
        if(readCount == 0) {
    
    
            V(wrt);
        }
    V(mutex1);
}

// 寫者進程結構
reader_first_writer {
    
    
    P(wrt);
    /* writing operation */
    V(wrt);
}

寫者優先

寫者優先策略的規則如下:

如果有寫者申請寫數據,在申請之前已經開始讀取數據的可以繼續讀取,但是如果再有讀者申請讀取數據,則被拒絕,只有在所有的寫者寫完之後才可以讀取。

  1. 寫者執行緒的優先順序高於讀者執行緒。
  2. 當有寫者到來時應該阻塞讀者執行緒的隊列。
  3. 當有一個寫者正在寫時或在阻塞隊列時應當阻塞讀者進程的讀操作,直到所有寫者進程完成寫操作時放開讀者進程。

寫者優先解決方案:

在讀者優先的基礎上增加:

  • 信號量 rdrd.count 初值為 1,用於在至少有一個寫者準備訪問共享數據區時,禁止所有讀進程。
  • 計數器 writeCount,紀錄寫者數,初值為 0。writeCount 是多個寫者進程共享的變量,應該要在臨界區中,因此還要增加一個信號量 mutex2,用互斥信號量 mutex2 控制,mutex2.count 初值為 1。
  • 信號量 mutex3,初值為 1。因為在 rd 上不允許建立長隊列,否則寫者進程將無法跳過這個隊列,因此只允許一個讀進程在 rd 上排隊,其餘都先在 mutex3 上排隊。
int readCount, writeCount = 0;
semaphore mutex1, mutex2, mutex3, wrt, rd;
mutex1.count = 1;
mutex2.count = 1;
mutex3.count = 1;
wrt.count = 1;
rd.count = 1;

// 讀者進程結構
writer_first_reader {
    
    
    P(mutex3);
        P(rd);
            P(mutex1);
                readCount++;
                if(readCount == 1) {
    
    
                    P(wrt);
                }
            V(mutex1);
        V(rd);
    V(mutex3);

    /* reading operation */

    P(mutex1);
        readCount--;
        if(readCount == 0) {
    
    
            V(wrt);
        }
    V(mutex1);
}

// 寫者進程結構
writer_first_writer {
    
    
    P(mutex2);
        writeCount++;
        if(writeCount == 1) {
    
    
            P(rd);
        }
    V(mutex2);

    P(wrt);
    /* writing operation */
    V(wrt);

    P(mutex2);
        writeCount--;
        if(writeCount == 0) {
    
    
            V(rd);
        }
    V(mutex2);
}

猜你喜欢

转载自blog.csdn.net/weixin_46803507/article/details/111116042