1、條件變量
條件變量必須配合着互斥鎖使用,互斥鎖查看-》線程同步(互斥鎖)
1.1、條件變量概念
-
條件變量是什麼:
本身不是鎖,滿足某個條件,像加鎖一樣,造成阻塞,與互斥量配合,給多線程提供會所。 -
爲什麼要用條件變量:
在線程搶佔互斥鎖時,線程A搶到了互斥鎖,但是條件不滿足,線程A就會讓出互斥鎖讓給其他線程,然後等待其他線程喚醒他;一旦條件滿足,線程就可以被喚醒,並且拿互斥鎖去訪問共享區。經過這中設計能讓進程運行更穩定。
1.2、條件變量控制原語
pthread_cond_t 用於創建條件變量
pthread_cond_init 用於初始化條件變量
pthread_cond_destroy 用於銷燬條件變量
pthread_cond_wait 用於阻塞條件變量
pthread_cond_timedwait 用於計時阻塞條件變量
pthread_cond_signal 用於喚醒條件變量
pthread_cond_broadcast 用於廣播所有條件變量
1.2.1、條件變量——創建
條件變量的創建和互斥鎖一樣,擁有兩種創建方式:靜態創建、動態創建
- 靜態創建:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
注意:PTHREAD_COND_INITIALIZER是一個常量
- 動態創建
- 函數:int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
- 參數1:傳入一個條件變量
- 參數2:屬性設置,一般都是寫NULL
- 返回值:成功返回0,失敗返回錯誤碼。
- 使用案例:pthread_cond_init(cond, NULL);
1.2.2、條件變量——銷燬
- 函數:int pthread_cond_destroy(pthread_cond_t *cond) ;
- 參數1:傳入一個條件變量
- 返回值:成功返回0,失敗返回錯誤碼
- 注意:要在使用之前,你要先確保沒有線程在等待,不然會返回EBUSY。
1.2.3、條件變量——等待
- 普通等待
- 函數:int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
- 參數1:傳入一個條件變量
- 參數2:傳入一個互斥鎖
- 作用:將互斥鎖讓給其他線程使用,如果被喚醒就去拿互斥鎖(如果互斥鎖有其他線程再用就阻塞)
- 計時等待(對我個人來說,我沒怎麼用到這個)
- 函數:int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,const struct timespec *abstime);
- 參數1:傳入一個條件變量
- 參數2:傳入一個互斥鎖
- 函數3:傳入struct timespec 類型的時間結構體
- 返回值:成功返回0,失敗返回錯誤碼
- 作用:在指定的時間內,這函數的作用和pthread_cond_wait函數一樣,只是說過了這個時間,還沒被喚醒就返回一個ETIMEOUT,並且讓互斥鎖再解鎖,讓其他線程搶佔。
1.2.4、條件變量——喚醒
在代碼中,喚醒和等待一般是配套使用的,就好比互斥鎖中的拿鎖和解鎖的關係。
-
喚醒至少一個線程的條件變量
- 函數:int pthread_cond_signal(pthread_cond_t *cptr);
- 參數1:傳入一個條件變量
- 返回值:成功返回0,失敗返回錯誤碼。
-
廣播(喚醒全部線程的條件變量)
- 函數:int pthread_cond_broadcast (pthread_cond_t * cptr);
- 參數1:傳入一個條件變量
- 返回值:成功返回0,失敗返回錯誤碼。
1.3、示例代碼——生產者消費者模型
在這示例代碼會建立一個對鏈表操作的生產者消費者模型。這一般只能有兩個線程,如果需要多個線程參加,可以看後面的線程同步之信號量,生產者消費者模型如下圖所示:
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
//定義鏈表的節點
struct msg
{
struct msg *next;
int num;
};
//建立全局的一個頭結點
struct msg *head;
//靜態建立一個條件變量has_product,放在全局裏
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
//靜態建立一個互斥鎖lock,放在全局裏
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
//聲明一下兩個執行函數
void *producer(void *p);
void *consumer(void *p);
//主函數
int main(void)
{
pthread_t pid, cid;
//建立一個隨機種子
srand(time(NULL));
//創建生產者線程和消費者線程
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
//回收兩個線程
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
void *consumer(void *p)
{
//建立一個臨時的鏈表的遊標mp
struct msg *mp;
for (;;)
{
pthread_mutex_lock(&lock);
while (head == NULL)
{
//消費者線程拿到互斥鎖後等待,並且把互斥鎖讓出來。
pthread_cond_wait(&has_product, &lock);
}
//鏈表移動
mp = head;
head = mp->next; pthread_mutex_unlock(&lock);
//打印當前節點的num
printf("Consume %d\n", mp->num);
//刪除遊標
free(mp);
//隨機延時
sleep(rand() % 5);
}
}
void *producer(void *p)
{
struct msg *mp;
for (;;)
{
//給節點開闢空間
mp = malloc(sizeof(struct msg));
//隨機賦值num 0-1000的值
mp->num = rand() % 1000 + 1;
printf("Produce %d\n", mp->num);
//給互斥鎖加鎖(防止其他線程搶佔)
pthread_mutex_lock(&lock);
//移動遊標
mp->next = head;
head = mp;
//互斥鎖解鎖,讓其他線程搶佔
pthread_mutex_unlock(&lock);
//喚醒消費者線程
pthread_cond_signal(&has_product);
//隨機睡眠
sleep(rand() % 5);
}
}