條件變量
條件變量給了線程以無競爭的方式等待特定條件發生。條件變量是和互斥量一起使用的,條件變量是由互斥量保護的。這麼講,大家可能不明白,這條件變量有什麼用?幹什麼的?還是結合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兩步是一個原子操作)
當然如果條件滿足了,那麼就不需要釋放鎖。所以釋放鎖這一步和等待條件滿足一定是一起執行(指原子操作)。 -
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()也被放到了臨界區。這是因爲可能在兩個線程中分別執行對同一塊內存的申請和釋放。這就會出錯。所以說,在評估鎖的粒度的時候,一定要小心,並且謹慎。