條件變量是用來等待線程而不是上鎖的,條件變量通常和互斥鎖一起使用。條件變量之所以要和互斥鎖一起使用,主要是因為互斥鎖的一個明顯的特點就是它只有兩種狀態(tài):鎖定和非鎖定,而條件變量可以通過允許線程阻塞和等待另一個線程發(fā)送信號來彌補互斥鎖的不足,所以互斥鎖和條件變量通常一起使用。
當條件滿足的時候,線程通常解鎖并等待該條件發(fā)生變化,一旦另一個線程修改了環(huán)境變量,就會通知相應(yīng)的環(huán)境變量喚醒一個或者多個被這個條件變量阻塞的線程。這些被喚醒的線程將重新上鎖,并測試條件是否滿足。一般來說條件變量被用于線程間的同步;當條件不滿足的時候,允許其中的一個執(zhí)行流掛起和等待。
簡而言之,條件變量本身不是鎖,但它也可以造成線程阻塞,通常與互斥鎖配合使用,給多線程提供一個會合的場所。
條件變量的優(yōu)點:
相較于mutex而言,條件變量可以減少競爭。如果僅僅是mutex,那么,不管共享資源里有沒數(shù)據(jù),生產(chǎn)者及所有消費都全一窩蜂的去搶鎖,會造成資源的浪費。
如直接使用mutex,除了生產(chǎn)者、消費者之間要競爭互斥量以外,消費者之間也需要競爭互斥量,但如果匯聚(鏈表)中沒有數(shù)據(jù),消費者之間競爭互斥鎖是無意義的。有了條件變量機制以后,只有生產(chǎn)者完成生產(chǎn),才會引起消費者之間的競爭。提高了程序效率。
主要應(yīng)用函數(shù):
pthread_cond_init函數(shù)
pthread_cond_destroy函數(shù)
pthread_cond_wait函數(shù)
pthread_cond_timedwait函數(shù)
pthread_cond_signal函數(shù)
pthread_cond_broadcast函數(shù)
以上6 個函數(shù)的返回值都是:成功返回0, 失敗直接返回錯誤號。
pthread_cond_t類型:用于定義條件變量,比如:pthread_cond_t cond;
函數(shù)原型:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
函數(shù)作用:
初始化一個條件變量
參數(shù)說明:
cond:條件變量,調(diào)用時應(yīng)傳&cond給該函數(shù)
attr:條件變量屬性,通常傳NULL,表示使用默認屬性
也可以使用靜態(tài)初始化的方法,初始化條件變量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
函數(shù)原型:
int pthread_cond_destroy(pthread_cond_t *cond);
函數(shù)作用:
銷毀一個條件變量
函數(shù)原型:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
函數(shù)作用:
阻塞等待一個條件變量。具體而言有以下三個作用:
其中1、2.兩步為一個原子操作。
函數(shù)原型:
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
函數(shù)作用:
限時等待一個條件變量
參數(shù)說明:
前兩個比較好理解,重點說明第三個參數(shù)。
這里有個struct timespec結(jié)構(gòu)體,可以在man sem_timedwait中查看。結(jié)構(gòu)體原型如下:
struct timespec {
? time_t tv_sec; /* seconds */ 秒
? long tv_nsec; /* nanosecondes*/ 納秒
}
struct timespec定義的形參abstime是個絕對時間。注意,是絕對時間,不是相對時間。什么是絕對時間?2018年10月1日10:10:00,這就是一個絕對時間。什么是相對時間?給洗衣機定時30分鐘洗衣服,就是一個相對時間,也就是說從當時時間開始計算30分鐘,諸如此類。
如:time(NULL)返回的就是絕對時間。而alarm(1)是相對時間,相對當前時間定時1秒鐘。
adstime所相對的時間是相對于1970年1月1日00:00:00,也就是UNIX計時元年。
下面給出一個錯誤用法:
struct timespec t = {1, 0};
pthread_cond_timedwait (&cond, &mutex, &t);
這種用法只能定時到 1970年1月1日 00:00:01秒,想必這個時間大家都還沒出生。
正確用法:
time_t cur = time(NULL); 獲取當前時間。
struct timespec t; 定義timespec 結(jié)構(gòu)體變量t
t.tv_sec = cur+1; 定時1秒
pthread_cond_timedwait (&cond, &mutex, &t); 傳參
函數(shù)原型:
int pthread_cond_signal(pthread_cond_t *cond);
函數(shù)作用:
喚醒至少一個阻塞在條件變量上的線程
函數(shù)原型:
int pthread_cond_broadcast(pthread_cond_t *cond);
函數(shù)作用:
喚醒全部阻塞在條件變量上的線程
不管是什么語言,只要提到線程同步,一個典型的案例就是生產(chǎn)者消費者模型。在Linux環(huán)境下,借助條件變量來實現(xiàn)這一模型,是比較常見的一種方法。
假定有兩個線程,一個模擬生產(chǎn)者行為,一個模擬消費者行為。兩個線程同時操作一個共享資源(一般稱之為匯聚),生產(chǎn)向其中添加產(chǎn)品,消費者從中消費掉產(chǎn)品。
看如下示例,使用條件變量模擬生產(chǎn)者、消費者問題:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
typedef struct msg {
struct msg *next;
int num;
}msg_t;
msg_t *head = NULL;
msg_t *mp = NULL;
/* 靜態(tài)初始化 一個條件變量 和 一個互斥量*/
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
void *th_producer(void *arg)
{
while (1) {
mp = malloc(sizeof(msg_t));
mp->num = rand() % 1000; //模擬生產(chǎn)一個產(chǎn)品
printf("--- produce: %d --------\n", mp->num);
pthread_mutex_lock(&mutex);
mp->next = head;
head = mp;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&has_product); //喚醒線程去消費產(chǎn)品
sleep(rand() % 5);
}
return NULL;
}
void *th_consumer(void *arg)
{
while (1) {
pthread_mutex_lock(&mutex);
while (head == NULL) { //如果鏈表里沒有產(chǎn)品,就沒有搶鎖的必要,一直阻塞等待
pthread_cond_wait(&has_product, &mutex);
}
mp = head;
head = mp->next; //模擬消費掉一個產(chǎn)品
pthread_mutex_unlock(&mutex);
printf("========= consume: %d ======\n", mp->num);
free(mp);
mp = NULL;
sleep(rand() % 5);
}
return NULL;
}
int main()
{
pthread_t pid, cid;
srand(time(NULL));
pthread_create(&pid, NULL, th_producer, NULL);
pthread_create(&cid, NULL, th_consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
運行結(jié)果:
更多精彩內(nèi)容,請關(guān)注公眾號良許Linux,公眾內(nèi)回復(fù)1024可免費獲得5T技術(shù)資料,包括:Linux,C/C++,Python,樹莓派,嵌入式,Java,人工智能,等等。公眾號內(nèi)回復(fù)進群,邀請您進高手如云技術(shù)交流群。
公眾號:良許Linux