Linux基礎——線程同步之條件變量

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); 
	} 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章