線程同步之條件變量(pthread_cond_wait)

條件變量

條件變量給了線程以無競爭的方式等待特定條件發生。條件變量是和互斥量一起使用的,條件變量是由互斥量保護的。這麼講,大家可能不明白,這條件變量有什麼用?幹什麼的?還是結合pthread_cond_wait()函數來分析一下吧!

下面給出本文講使用的的有關條件變量的函數。

單刀直入,我們需要分析的重點就是pthread_cond_wait()函數。而pthread_cond_timewait()只是比它多了個超時而已。

pthread_cond_wait()函數等待條件變量變爲真的。它需要兩個參數,第一個參數就是條件變量,而第二個參數mutex是保護條件變量的互斥量。也就是說這個函數在使用的時候需要配合pthread_mutex_lock()一起使用。即:

pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);

 因此,這個函數的功能可以總結如下:

  1. 等待條件變量滿足;

  2. 把獲得的鎖釋放掉;(注意:1,2兩步是一個原子操作
    當然如果條件滿足了,那麼就不需要釋放鎖。所以釋放鎖這一步和等待條件滿足一定是一起執行(指原子操作)。

  3. pthread_cond_wait()被喚醒時,它解除阻塞,並且嘗試獲取鎖(不一定拿到鎖)。因此,一般在使用的時候都是在一個循環裏使用pthread_cond_wait()函數,因爲它在返回的時候不一定能拿到鎖(這可能會發生餓死情形,當然這取決於操作系統的調度策略)。

這個pthread_cond_wait()函數可以被pthread_cond_signal()或者是pthread_cond_broadcast()函數喚醒。不同之處在於,pthread_cond_signal()可以喚醒至少一個線程;而pthread_cond_broadcast()則是喚醒等待該條件滿足的所有線程。在使用的時候需要注意,一定是在改變了條件狀態以後再給線程發信號。

 pthread_cond_init()函數是用來初始化pthread_cond_t類型的條件變量的,和之前的函數類似,在動態分配pthread_cond_t類型的變量的時候,只能使用pthread_cond_init它來初始化(因爲POSIX標準只規定了接口長什麼樣子,沒規定怎麼實現,所以pthread_cond_t這個數據類型可能被實現爲結構體,爲了最大化可移植性,就搞了個init函數來動態初始化)。

 pthread_cond_destroy()函數就是用來釋放條件變量的(反初始化),再次提醒,不是釋放內存空間的。

 pthread_cond_timedwait()函數和 pthread_cond_wait()函數比起來多一個時間參數,這個參數可以指定等待這個條件多長時間,他是通過timespec結構指定。所以用起來比較麻煩,不在這裏細述。

好了,關於這些函數就介紹完了,下面看一段代碼,這段代碼將演示一個非常經典的同步問題:生產者——消費者模型

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

//鏈表的結點
struct msg
{
    int num; 
    struct msg *next; 
};
 
struct msg *head = NULL;    //頭指針
struct msg *temp = NULL;    //節點指針

//靜態方式初始化互斥鎖和條件變量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_producer = PTHREAD_COND_INITIALIZER;
 
void *producer(void *arg)
{
    while (1)   //線程正常不會解鎖,除非收到終止信號
	{
        pthread_mutex_lock(&mutex);         //加鎖

        temp = malloc(sizeof(struct msg));
        temp->num = rand() % 100 + 1;
        temp->next = head;
        head = temp;                        //頭插法
        printf("---producered---%d\n", temp->num);

        pthread_mutex_unlock(&mutex);       //解鎖
        pthread_cond_signal(&has_producer); //喚醒消費者線程
        usleep(rand() % 3000);              //爲了使該線程放棄cpu,讓結果看起來更加明顯。
    }
 
    return NULL;
}
 
void *consumer(void *arg)
{
    while (1)       //線程正常不會解鎖,除非收到終止信號
	{
        pthread_mutex_lock(&mutex);     //加鎖
        while (head == NULL)            //如果共享區域沒有數據,則解鎖並等待條件變量
	    {
            pthread_cond_wait(&has_producer, &mutex);   //我們通常在一個循環內使用該函數
        }
        temp = head;
        head = temp->next;
        printf("------------------consumer--%d\n", temp->num);
        free(temp);                     //刪除節點,頭刪法
        temp = NULL;                    //防止野指針
        pthread_mutex_unlock(&mutex);   //解鎖

        usleep(rand() % 3000);          //爲了使該線程放棄cpu,讓結果看起來更加明顯。
    }
 
    return NULL;
}
 
int main(void)
{
    pthread_t ptid, ctid;
    srand(time(NULL));      //根據時間搖一個隨機數種子

    //創建生產者和消費者線程
    pthread_create(&ptid, NULL, producer, NULL);
    pthread_create(&ctid, NULL, consumer, NULL);

    //主線程回收兩個子線程
    pthread_join(ptid, NULL);
    pthread_join(ctid, NULL);
 
    return 0;
}

程序運行結果如下所示:

這是截取的一部分運行結果,需要注意的是,malloc()放到了臨界區,free()也被放到了臨界區。這是因爲可能在兩個線程中分別執行對同一塊內存的申請和釋放。這就會出錯。所以說,在評估鎖的粒度的時候,一定要小心,並且謹慎。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章