1. 程式人生 > >3.條件變數(1.互斥鎖;2,讀寫鎖)

3.條件變數(1.互斥鎖;2,讀寫鎖)

先介紹幾個api:

pthread_cond_t表示多執行緒的條件變數,用於控制執行緒等待和就緒的條件。

一:條件變數的初始化:

條件變數和互斥鎖一樣,都有靜態動態兩種建立方式,

靜態方式使用PTHREAD_COND_INITIALIZER常量初始化。

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

動態方式初始化:

1 首先要new或者malloc一個pthread_cond_t型別變數,

用完後記得delete或者free掉。

2

動態方式呼叫pthread_cond_init()函式,API定義如下:

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

 

二:條件變數的銷燬

 

登出一個條件變數需要呼叫pthread_cond_destroy(),只有在沒有執行緒在該條件變數上等待的時候才能登出這個條件變數,否則返回EBUSY。

因為Linux實現的條件變數沒有分配什麼資源,所以登出動作只包括檢查是否有等待執行緒。API定義如下:

int pthread_cond_destroy(pthread_cond_t *cond)

 

new開闢的pthread_cond_t記得在呼叫pthread_cond_destroy()後呼叫delete或者free銷燬掉。

 

三:等待和觸發

 

1條件等待

int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex);

 

2時間等待

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

 

 其中計時等待方式如果在給定時刻前條件沒有滿足,則返回ETIMEOUT,結束等待,

其中abstime以與time()

系統呼叫相同意義的絕對時間形式出現,0表示格林尼治時間1970年1月1日0時0分0秒。

 

無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個執行緒同時請求pthread_cond_wait()(或pthread_cond_timedwait(),下同)

競爭條件(Race Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)

或者適應鎖(PTHREAD_MUTEX_ADAPTIVE_NP),且在呼叫pthread_cond_wait()前必須由本執行緒加鎖(pthread_mutex_lock()),

而在更新條件等待佇列以前,mutex保持鎖定狀態,並在執行緒掛起進入等待前解鎖。

在條件滿足從而離開pthread_cond_wait()之前,mutex將被重新加鎖,以與進入pthread_cond_wait()前的加鎖動作對應。

 

使用pthread_cond_wait方式如下:

    pthread _mutex_lock(&mutex)

    while或if(執行緒執行的條件是否成立)

          pthread_cond_wait(&cond, &mutex);

    執行緒執行

    pthread_mutex_unlock(&mutex);

 

3

激發條件有兩種形式,pthread_cond_signal()啟用一個等待該條件的執行緒,存在多個等待執行緒時按入隊順序啟用其中一個;

而pthread_cond_broadcast()則啟用所有等待執行緒。

 

 上面就是多執行緒條件變數的基礎知識,下面著重闡述下為什麼呼叫pthread_cond_wait之前要加鎖,以及pthread_cond_wait內部

呼叫了什麼。

首先解釋為什麼在等待前加鎖,因為執行緒隸屬於程序,執行緒共享程序的資源,如果不進行加鎖,就會造成多個執行緒同時(相對意義的同時,

可能一個執行緒在函式A中更改此共享資源,此時函式A沒結束,另一個執行緒也訪問了這個共享資源)

訪問這塊共享的資源,如果對臨界區的內容進行更改,那麼兩個執行緒就會造成資料的不準確。所以在更改臨界資源的時候要枷鎖。而呼叫

pthread_cond_wait之前要加鎖也是為了避免多個執行緒競爭一個條件,造成共享的資源被多個執行緒更改。所以需要互斥的訪問共有資源,

那麼在pthread_cond_wait之前需要加鎖,避免別的執行緒更改共有資源。

接下來思考pthread_cond_wait內部做了哪些操作。

在pthread_cond_wait呼叫之前,執行緒呼叫pthread_mutex_lock,設定鎖,如果條件不滿足,那麼該執行緒處於阻塞等待的狀態。別的執行緒

發現條件滿足後會呼叫pthread_cond_signal或pthread_cond_broadcast通知他。那麼問題出來了,如果該執行緒不解鎖,別的執行緒是沒辦法

更改共享資源的,也就沒辦法設定條件變數使其滿足該執行緒的等待條件,出現死鎖。所以,pthread_cond_wait會在內部進行解鎖操作。別的

執行緒可以訪問共享資源,更改條件觸發該執行緒,是該執行緒從阻塞狀態變為就緒。慢一點,還有一個重要的步驟,pthread_cond_wait會將該執行緒

放到執行緒等待佇列裡,那麼是在放到等待佇列之前解鎖還是放到等待佇列之後才解鎖呢?

對於這點apue給出的解釋:The mutex passed to pthread_cond_wait protects the condition.The caller passes it locked to 

the function, which then atomically places the calling thread on the list of threads waiting for the condition and unlocks 

the mutex. This closes the window between the time that the condition is checked and the time that the

 thread goes to sleep waiting for the condition to change, so that the thread doesn't miss a change in the condition. 

When pthread_cond_wait returns, the mutex is again locked.

 這段話的意思是mutex傳遞給pthread_cond_wait 用於保護條件,呼叫者將mutex傳遞給pthread_cond_wait,

pthread_cond_wait 會自動將呼叫該函式的執行緒放到執行緒等待佇列上,等待條件並且解鎖。這種做法關閉了一段間隙,

這段間隙就是在我們檢測條件的時刻和將執行緒放到等待佇列休眠的時刻之間,這麼做該執行緒不會錯過條件的改變。而當

pthread_cond_wait 返回時,mutex又被上鎖了。

所以,pthread_cond_wait內部的操作順序是將執行緒放到等待佇列,然後解鎖,等條件滿足時進行加鎖,然後返回。

整理下pthread_cond_wait內部操作

1,執行緒放在等待佇列上,解鎖

2,等待 pthread_cond_signal或者pthread_cond_broadcast訊號之後去競爭鎖

3,若競爭到互斥索則加鎖。

 

使用流程

等待執行緒:

pthread_mutex_lock(&mutex);

if(條件不滿足)

  pthread_cond_wait(&cond, &mutex);

//處理共享資源

pthread_mutex_unlock(&mutex);

 

啟用執行緒:

pthread_mutex_lock(&mutex);

pthread_cond_signal(&cond);

pthread_mutex_unlock(&mutex);

 

下面寫了一個例子

複製程式碼

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <iostream>
using namespace std;

int count = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 
//該函式增加count數值
void * creator(void * arg)
{
    cout << "creator add lock" << endl;
    pthread_mutex_lock(&mutex);

    count ++;

    cout << "in creator count is : " << count << endl;
    //條件滿足時傳送訊號
    if(count > 0)
    {

        pthread_cond_signal(&cond);
    }

    
    cout << "creator release lock" << endl;
    
    pthread_mutex_unlock(&mutex);

    return NULL;

}

//該函式減少count數值
void * consumer(void * arg)
{
    cout << "consumer add lock" << endl;

    pthread_mutex_lock(&mutex);
    //當條件不滿足時等待
    if(count <= 0)
    {
        cout << "begin wait" << endl;
        pthread_cond_wait(&cond,&mutex);
        cout << "end wait" << endl;
    }

    count --;

    cout << "in consumer count is " << count << endl;

    pthread_mutex_unlock(&mutex);

    cout << "consumer release lock" << endl;
    
    return NULL;
    
}


int main()
{
    //兩個執行緒,一個生產者執行緒一個消費者執行緒
    pthread_t createthread,consumethread;

     pthread_create(&consumethread, NULL, consumer, NULL);
   sleep(2);
    pthread_create(&createthread, NULL, creator, NULL);
    
    //主程序等待兩個執行緒結束
    pthread_join(createthread, NULL);
    pthread_join(consumethread, NULL);
    return 0;
}

複製程式碼

因為消費者執行緒先跑起來,會等待生產者增加count數量,所以列印輸出結果如下

 

下面將消費者和生產者執行緒增加幾個,creater和consumer內部用迴圈處理,

這樣就能看出效果了。

 

複製程式碼

void * creator(void * arg)
{
    int i = 0;
    while(i<300)
    {

        i++;
        cout << "creator add lock" << endl;
        pthread_mutex_lock(&mutex);

        count ++;

        cout << "in creator count is : " << count << endl;

        if(count > 0)
        {

            pthread_cond_signal(&cond);
        }

    
        cout << "creator release lock" << endl;
    
        pthread_mutex_unlock(&mutex);

    }

    return NULL;

}





void * consumer(void * arg)
{
    int i = 0;
    while(i < 100)
    {
        
        i++;
        cout << "consumer add lock" << endl;

        pthread_mutex_lock(&mutex);

        if(count <= 0)
        {
            cout << "begin wait" << endl;
            pthread_cond_wait(&cond,&mutex);
            cout << "end wait" << endl;
        }

        count --;

        cout << "in consumer count is " << count << endl;

        pthread_mutex_unlock(&mutex);

        cout << "consumer release lock" << endl;
    }
    
    return NULL;
    
}


int main()
{
     pthread_t createthread[2],consumethread[3];

     for(int i = 0; i < 3; i++)
     {
        pthread_create(&consumethread[i], NULL, consumer, NULL);
     }
     
     for(int i = 0; i < 2; i++)
     {
        pthread_create(&createthread[i], NULL, creator, NULL);
         }
     
     for(int i = 0; i < 2; i++)
      {
        pthread_join(createthread[i], NULL);
         }

     for(int i = 0; i < 3; i++)
     {
         pthread_join(consumethread[i], NULL);
     }

    
    return 0;

}

複製程式碼

 

擷取一部分結果截圖,可以看出數字是連續變動的,而且

加鎖解鎖內數字才變動,說明我們對鎖和條件變數使用合理。