1. 程式人生 > >linux同步機制條件變數

linux同步機制條件變數

目錄

 

1、條件變數

2、條件變數API

2.1 pthread_cond_init

2.2 pthread_cond_wait

2.3 pthread_cond_timedwait

2.4 pthread_cond_signal

2.5 pthread_cond_broadcast

2.6 pthread_cond_destroy

3、喚醒丟失問題

4、生產者、消費者例子


1、條件變數

linux多執行緒的環境中使用互斥鎖mutex來保護變數的同步,但互斥鎖只有兩種狀態鎖定、非鎖定,如果某個變數的條件沒有滿足就對變數進行加鎖處理就會白白的損耗CPU,這時可以使用條件變數pthread_cond_t,條件變數pthread_cond_t一般是和互斥鎖配合使用,當條件不滿足條件變數就會掛起並釋放互斥鎖,這時就不會損耗CPU,直到條件滿足條件變數就會重新加互斥鎖保護。

2、條件變數API

2.1 pthread_cond_init

條件變數的初始化有動態初始化、靜態初始化兩種方式,呼叫pthread_cond_init就是動態初始化條件變數,catter是條件變數的屬性,如果是預設屬性就是NULL,我們一般是設定為預設屬性。

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cv,
                      const pthread_condattr_t *cattr);
返回值:函式成功返回0;任何其他返回值都表示錯誤

靜態初始化是呼叫巨集PTHREAD_COND_INITIALIZER,預設是預設模式。

pthread_cond_t cv = PTHREAD_COND_INITIALIZER;

2.2 pthread_cond_wait

pthread_cond_wait函式會釋放互斥鎖mutex,然後執行緒阻塞掛起,直到被pthread_cond_singal函式、pthread_cond_broadcast喚醒,或者被訊號中斷後喚醒。

pthread_cond_wait呼叫格式如下:

pthread_mutex_lock();
while(condition_is_false)
    pthread_cond_wait();
pthread_mutex_unlock;

這裡用while而不是用if判斷條件是否滿足,因為條件阻塞時可能被一個訊號中斷喚醒,但這時條件還沒有準備好,所以要再次呼叫pthread_cond_wait()掛起執行緒。

呼叫pthread_cond_wait處理過程:

釋放互斥鎖 --> 程序阻塞掛起

pthread_cond_wait收到喚醒訊號處理過程:

收到喚醒訊號-->對互斥鎖加鎖

pthread_cond_wait函式:

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
返回值:函式成功返回0;任何其他返回值都表示錯誤

2.3 pthread_cond_timedwait

pthread_cond_timedwait在pthread_cond_wait的基礎上新增一個超時時間,如果超時pthread_cond_timedwait就會返回這時會對互斥鎖重新加鎖。

#include <pthread.h>
#include <time.h>
int pthread_cond_timedwait(pthread_cond_t *cv,
pthread_mutex_t *mp, const structtimespec * abstime);
返回值:函式成功返回0;任何其他返回值都表示錯誤

abstime使用:

pthread_timestruc_t to;
to.tv_sec = time(NULL) + TIMEOUT;   //秒
to.tv_nsec = 0;                    //毫秒

2.4 pthread_cond_signal

pthread_cond_signal用來釋放阻塞在條件變數上的執行緒,如果有對個執行緒被條件變數阻塞,pthread_cond_signal只釋放一個執行緒,至於釋放那個由系統排程策略決定。條件變數必須在互斥鎖 的保護下使用,否則釋放條件變數可能在鎖定條件變數之前呼叫那麼就會進入死鎖狀態。

pthread_cond_signal:

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cv);
返回值:函式成功返回0;任何其他返回值都表示錯誤

2.5 pthread_cond_broadcast

pthread_cond_broadcast會喚醒所有阻塞條件變數cv上的執行緒。當所有執行緒被喚醒就會進入加互斥鎖的競爭中。

#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cv);
返回值:函式成功返回0;任何其他返回值都表示錯誤

2.6 pthread_cond_destroy

pthread_cond_destroy會銷燬條件變數cv,釋放條件變數使用的空間。

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cv);
返回值:函式成功返回0;任何其他返回值都表示錯誤

3、喚醒丟失問題

線上程未獲得相應的互斥鎖時呼叫pthread_cond_signal或pthread_cond_broadcast函式可能會引起喚醒丟失問題。

喚醒丟失往往會在下面的情況下發生:

  1. 一個執行緒呼叫pthread_cond_signal或pthread_cond_broadcast函式;
  2. 另一個執行緒正處在測試條件變數和呼叫pthread_cond_wait函式之間;
  3. 沒有執行緒正在處在阻塞等待的狀態下。

 

4、生產者、消費者例子

(1)不使用條件變數

不使用條件變數:

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

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

typedef struct node{
	int data;
	struct node * next;
	
}*node_p;
node_p head = NULL;

node_p init_node(int data){
	
	node_p tmp;	
	tmp = (node_p) malloc(sizeof(struct node));
	if(tmp == NULL){
		printf("memeroy out!\n");
		return NULL;
	}
	
	tmp->data = data;
	return tmp;
}

void push_node(node_p head, int data){
	
	node_p tmp = init_node(data);
	if(tmp == NULL){
		printf("push_node err!\n");
		return;
	}
	
	tmp->next = head->next;
	head->next = tmp;
}

int pop_node(node_p head){
	int ret = -1;
	
	if(head->next){
		node_p tmp;
		tmp = head->next;
		head->next = tmp->next;
		ret = tmp->data;
		free(tmp);
	}
	
	return ret;
}

void show_node(node_p head){
	node_p tmp;
	tmp = head->next;
	
	while(tmp){
		printf("data:%d\n", tmp->data);
		tmp = tmp->next;
	}
}
void *consumer(void *argv){
	
	while(1){
		sleep(1);
		pthread_mutex_lock(&lock);
		if(head->next == NULL){
			printf("producter not read...\n");
		}else{
			printf("consumer:%d\n", pop_node(head));
		}
		pthread_mutex_unlock(&lock);
	}
}

void *producter(void *argv){
	int data;
	while(1){
		sleep(5);
		pthread_mutex_lock(&lock);
		data = rand()%2019;
		push_node(head, data);
		printf("producter:%d\n", data);
		pthread_mutex_unlock(&lock);
	}
}

int main(){
	pthread_t t1, t2;
	
	head = init_node(0);	
	if(head == NULL){
		printf("init head fail...\n");
		return -1;
	}
	
	pthread_create(&t1, NULL, consumer, NULL);
	pthread_create(&t2, NULL, producter, NULL);
	
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
}

執行結構如下:

[email protected]:/home/c_test/cond# gcc producter_consumer.c -lpthread
[email protected]:/home/c_test/cond# ./a.out
producter not read...
producter not read...
producter not read...
producter not read...
producter:1957
consumer:1957
producter not read...
producter not read...
producter not read...
producter not read...
producter:766
consumer:766
producter not read...
producter not read...
producter not read...
producter not read...
producter:1050
consumer:1050

我們可以看到當生產者沒有準備好時,消費者就會一致佔用CPU,這樣是在浪費CPU時間。

(2)加條件變數

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

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

typedef struct node{
	int data;
	struct node * next;
	
}*node_p;
node_p head = NULL;

node_p init_node(int data){
	
	node_p tmp;	
	tmp = (node_p) malloc(sizeof(struct node));
	if(tmp == NULL){
		printf("memeroy out!\n");
		return NULL;
	}
	
	tmp->data = data;
	return tmp;
}

void push_node(node_p head, int data){
	
	node_p tmp = init_node(data);
	if(tmp == NULL){
		printf("push_node err!\n");
		return;
	}
	
	tmp->next = head->next;
	head->next = tmp;
}

int pop_node(node_p head){
	int ret = -1;
	
	if(head->next){
		node_p tmp;
		tmp = head->next;
		head->next = tmp->next;
		ret = tmp->data;
		free(tmp);
	}
	
	return ret;
}

void show_node(node_p head){
	node_p tmp;
	tmp = head->next;
	
	while(tmp){
		printf("data:%d\n", tmp->data);
		tmp = tmp->next;
	}
}
void *consumer(void *argv){
	
	while(1){
		sleep(1);
		pthread_mutex_lock(&lock);
		while(head->next == NULL){
			printf("producter not read...\n");
			pthread_cond_wait(&cond, &lock);
			break;
		}
		
		printf("consumer:%d\n", pop_node(head));
		pthread_mutex_unlock(&lock);
	}
}

void *producter(void *argv){
	int data;
	while(1){
		sleep(5);
		pthread_mutex_lock(&lock);
		
		data = rand()%2019;
		push_node(head, data);	
		printf("producter:%d\n", data);	
		
		pthread_cond_signal(&cond);
		pthread_mutex_unlock(&lock);
		
	}
}

int main(){
	pthread_t t1, t2;
	
	head = init_node(0);	
	if(head == NULL){
		printf("init head fail...\n");
		return -1;
	}
	
	pthread_create(&t1, NULL, consumer, NULL);
	pthread_create(&t2, NULL, producter, NULL);
	
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	pthread_cond_destroy(&cond);
}

執行結果:

[email protected]:/home/c_test/cond# gcc producter_consumer_cond.c -lpthread
[email protected]:/home/c_test/cond# ./a.out
producter not read...
producter:1957
consumer:1957
producter not read...
producter:766
consumer:766
producter not read...
producter:1050
consumer:1050
producter not read...
producter:1165
consumer:1165

加了條件變數後當條件沒有準備好時wait_cond_wait就會釋放互斥鎖消費者執行緒掛起,消費者讓出CPU給生產者而不會白白佔用CPU。