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);
}

運行結構如下:

root@ubuntu:/home/c_test/cond# gcc producter_consumer.c -lpthread
root@ubuntu:/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);
}

運行結果:

root@ubuntu:/home/c_test/cond# gcc producter_consumer_cond.c -lpthread
root@ubuntu:/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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章