操作系統學習筆記(二) 信號量、條件變量、互斥量、讀寫鎖


在有了進程和線程的模型之后,一個很大的問題就擺在眼前:進程和線程的執行順序是不可預知的,那么,如何使得兩個進程按照我們想要的順序執行,從而得出正確的結果呢?

競爭條件:兩個或者多個進程讀寫某些共享數據,最后的結果依賴於進程運行的精確時序。

臨界區:把對共享內存進行訪問的程序片段稱作臨界區。如果能使兩個進程不可能同時處於臨界區內,就能夠避免競爭。

先引入一個經典的進程同步問題:生產者-消費者問題

生產者-消費者問題:有一個緩沖區,一個(或多個)進程在生產某種產品,它生產的東西會放入緩沖區內;一個(或多個)進程在消費產品,它會從緩沖區內取走產品。當緩沖區滿時,生產者應當暫時停止生產;當緩沖區為空時,消費者應當暫時停止消費。

很顯然,這個問題用簡單的判斷緩沖區是否為0或N是無法解決的。如果在消費者判斷緩沖區為0時,恰好遇到了進程切換,生產者進程開始運行,此時應當喚醒消費者,然而這個信號丟失了,因為切換到消費者才進行了睡眠。這時,生產者會不斷運行,直到緩沖區滿,兩個進程全部睡眠,造成了死鎖。代碼如下:

#define N 1000
int count=0;
void producer(void)
{
    int item;
    while(TRUE)
    {
        item=produce_item();
        if(count==N) sleep();//一段時間后,緩沖區滿,生產者進程也睡眠了
        insert_item(item);
        count=count+1;
        if(count==1) wakeup(consumer);//設想判斷條件成立時,切換了進程,再次切回時,喚醒消費者進程,然而消費者進程此時沒有睡眠,信號丟失
    }        
}

void consumer(void)
{
    int item;
    while(TRUE)
    {
        if(count==0) sleep();//第一次count=1,消費者進程不會睡眠;第二次確實睡眠了
        item=remove_item();
        count=count-1;//此時緩沖區確實為空了 if(count==N-1) wakeup(producer);
        consume_item(item);
    }
}

 

一、信號量

信號量是一種數據結構,可以理解為一個用來計數的整數和一個隊列。整數用來記錄喚醒次數,而隊列被用來記錄因為該信號量而阻塞的進程。

信號量只支持兩種操作:P/V操作

P操作,可以理解為測試並減一。P(signal1),如果signal1大於0,那么把它減一,進程繼續執行;如果signal為0,那么執行P操作的進程將會被阻塞,從而變為阻塞態,添加到因為signal1信號而阻塞的進程隊列中。

V操作,可以理解為+1並喚醒。V(signal1)后,如果signal1本來就大於0,那么執行+1;如果有進程在該信號量上被阻塞,那么從隊列中根據某種策略選擇一個進程喚醒。如果多個進程在該信號量上阻塞,那么V操作后,signal1仍然可能為負數。

需要注意的是,P/V操作均應當是原子操作,即作為一個整體執行而不會被打斷。

有了信號量,我們再來看生產者-消費者問題:

#define N 1000
typedef int semaphore;
semaphore mutex=1;//控制對臨界區的訪問,其實就是互斥量
semaphore empty=N;//表示空槽的數量
semaphore full=0;//填滿的槽的數量
int count=0;
void producer(void)
{
    int item;
    while(TRUE)
    {
        item=produce_item();
        down(&empty);
        down(&mutex);//要改變共享區(緩沖區),加鎖
        insert_item(item);
        up(&mutex);//解鎖
        up(&full);
    }        
}

void consumer(void)
{
    int item;
    while(TRUE)
    {
        down(&full);
        down(&mutex);
        item=remove_item();
        up(&mutex);
        up(&empty);
        consume_item(item);
    }
}

有了信號量,這個問題就好解決多了:用信號量full、empty來表示已用和未用的數量,這樣不管是滿了還是空了,都不會造成死鎖的問題。mutex的操作就是我們接下來要介紹的互斥鎖。

 

二、互斥鎖

互斥量其實可以理解為一個簡化的信號量,它只有兩種狀態:0和1。互斥鎖是用來解決進程(線程)互斥問題的。所謂進程互斥,就是兩個進程實際上是一種互斥的關系,兩者不能同時訪問共享資源。

互斥量和信號量原理比較類似,一旦一個線程獲得了鎖,那么其它線程就無法訪問共享資源,從而被阻塞,直到該線程交還出了鎖的所有權,另外一個線程才能獲得鎖。

互斥鎖的例子就不再給出,上面程序中已經有了,下面的程序中也會出現。

 

三、條件變量

條件變量是另外一種同步機制,可以用於線程和管程中的進程互斥。通常與互斥量一起使用。

條件變量允許線程由於一些暫時沒有達到的條件而阻塞。通常,等待另一個線程完成該線程所需要的條件。條件達到時,另外一個線程發送一個信號,喚醒該線程。

條件變量對應的一組操作是pthread_cond_wait和pthread_cond_signal。

條件變量與互斥量一起使用,一般情況是:一個線程鎖住一個互斥量,然后當它不能獲得它期待的結果時,等待一個條件變量;最后另外一個線程向它發送信號,使得它可以繼續執行。

需要注意的是,pthread_cond_wait會暫時解開持有的互斥鎖。

 

四、讀寫鎖

讀寫鎖相對上面的問題會復雜一些,它被用來解決一個經典的問題:讀者-寫者問題

讀寫鎖與互斥量類似,不過讀寫鎖允許更高的並行性。互斥量要么是鎖住狀態要么是不加鎖狀態,而且一次只有一個線程可以對其加鎖。

下面的代碼考慮的是讀者優先的讀者-寫者問題,對於共享區域的讀寫規則如下:

1.只要有一個讀者在讀,后來的讀者可以進入共享區直接讀。

2.只要有一個讀者在讀,寫者就必須阻塞,直到最后一個讀者離開。

3.不考慮搶占式,寫者在寫時,即使有讀者到達,也會在就緒態等待。

 

typedef int semaphore;
semaphore mutex=1;    //互斥鎖,控制對rc的訪問
semaphore db=1;        //控制對數據庫的訪問
int rc=0;        //當前讀者計數

void reader(void)
{
    while(TRUE)
    {
        down(&mutex);//加鎖
        rc=rc+1;
        if(rc==1) down(&db);//第一個讀者,加鎖
        up(&mutex);
        read_data_base();
        down(&mutex);
        rc=rc-1;
        if(rc==0) up(&db);//最后一個讀者離開,解鎖
        up(&mutex);
        use_data_read();
    }
}

void writer(void)
{
    while(TRUE)
    {
        think_up_data();
        down(&db);//獲取數據庫訪問的鎖
        write_data_base();
        up(&db);
    }
}

 

這里,我們其實用了兩個互斥鎖來實現了讀寫鎖。一個互斥鎖用來保護共享區,另外一個互斥鎖用來保護讀者計數器。

 

讀寫鎖可以由三種狀態:讀模式下加鎖狀態寫模式下加鎖狀態不加鎖狀態。一次只有一個線程可以占有寫模式的讀寫鎖,但是多個線程可以同時占有讀模式的讀寫鎖。

在讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的線程都會被阻塞。當讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對它進行加鎖的線程都可以得到訪問權,但是如果線程希望以寫模式對此鎖進行加鎖,它必須阻塞直到所有的線程釋放讀鎖。雖然讀寫鎖的實現各不相同,但當讀寫鎖處於讀模式鎖住狀態時,如果有另外的線程試圖以寫模式加鎖,讀寫鎖通常會阻塞隨后的讀模式鎖請求。這樣可以避免讀模式鎖長期占用,而等待的寫模式鎖請求一直得不到滿足。

讀寫鎖非常適合於對數據結構讀的次數遠大於寫的情況。當讀寫鎖在寫模式下時,它所保護的數據結構就可以被安全地修改,因為當前只有一個線程可以在寫模式下擁有這個鎖。當讀寫鎖在讀狀態下時,只要線程獲取了讀模式下的讀寫鎖,該鎖所保護的數據結構可以被多個獲得讀模式鎖的線程讀取。

讀寫鎖也叫做共享-獨占鎖,當讀寫鎖以讀模式鎖住時,它是以共享模式鎖住的;當他以寫模式鎖住時,它是以獨占模式鎖住的。

五、總結

這里,主要是簡單總結一下這幾種同步量的用法。

1、互斥鎖只用在同一個線程中,用來給一個需要對臨界區進行讀寫的操作加鎖。

2、信號量與互斥量不同的地方在於,信號量一般用在多個進程或者線程中,分別執行P/V操作。

3、條件變量一般和互斥鎖同時使用,或者用在管程中。

4、互斥鎖,條件變量都只用於同一個進程的各線程間,而信號量(有名信號量)可用於不同進程間的同步。當信號量用於進程間同步時,要求信號量建立在共享內存區。

5、互斥鎖是為上鎖而優化的;條件變量是為等待而優化的; 信號量既可用於上鎖,也可用於等待,因此會有更多的開銷和更高的復雜性。

 

 

參考書籍:《現代操作系統》

 


免責聲明!

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



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