POSIX条件变量



1. 条件变量的使用情形

  • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了;
  • 例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中,这种情况就需要用到条件变量;

还是以简单的生产者和消费者为例:

通过以上的分析,条件变量破坏了资源的不可剥夺性进而避免了死锁,同时发现条件变量可以很好地解决生产者消费者问题,特别是针对无界缓冲区的情形,即有产品的时候允许消费者消费产品,无产品的时候不允许消费者消费产品。生产者生产过后则通知阻塞在产品情况下的消费者进行消费。实现该种条件模式的生产者消费者模型使用一个mutex互斥锁,一个cond条件变量即可。



2. 条件变量函数

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_init(pthread_cond_t *restrict_cond, const pthread_condattr_t *restrict_attr);
pthread_cond_destory(pthread_cond_t *cond);
pthread_cond_wait(pthread_cond_t *restrict_cond, pthread_mutex_t *restrict_mutex);
pthread_cond_timedwait(pthread_cond_t *restrict_cond, pthread_mutex_t *restrict_mutex,
					const struct timespec *restrict_abstime);
pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast(pthread_cond_t *cond);

3. 条件变量使用规范

/*等待条件代码*/
pthread_mutex_lock(&mutex);
while(cond is false)
	pthread_cond_wait(cond, mutex);
operate on shared variance
pthead_mutex_unlock(&mutex);

/*给条件发送信号代码*/
pthread_mutex_lock(&mutex);
set cond is true / operate on shared variance
pthread_cond_signal(cond); 
//pthread_cond_broadcast(cond);
pthread_mutex_unlock(&mutex);


4. 生产者消费者模型条件变量实现

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) 
	do {
		perror(m);
		exit(EXIT_FAILURE);
	} while(0)

#define CONSUMER_COUNT 1 //消费者个数
#define PRODUCER_COUNT 1 //生产者个数

pthread_cond_t g_cond = THREAD_COND_INITIALIZER; //条件变量
pthread_mutex_t g_mutex; //互斥锁,设置临界区

pthread_t g_thread[CONSUMER_COUNT + PRODUCER_COUNT]; //线程数组,存储生产者和消费者线程

int nready = 0; //共享变量,表示产品数量

void* consume(void *arg) {
	int num = (int)arg;
	while(1) {
		pthread_mutex_lock(&g_mutex);
		while(0 == nready) {
			printf("Consumer %d begin wait a condition...\n", num);
			pthread_cond_wait(&g_cond, &g_mutex); //等待条件变量满足
		}
		printf("Consumer %d end wait a condition...\n begin consume\n", num);
		--nready; //消费一个产品
		pthread_mutex_unlock(&g_mutex);	
		sleep(1);
	}
	return NULL;
}

void* produce(void *arg) {
	int num = (int)arg;
	while(1) {
		pthread_mutex_lock(&g_mutex);
		nready++; //生产产品
		if(nready > 0) pthread_cond_signal(&g_cond);
		printf("Producer %d signal...\n", num);
		pthread_mutex_unlock(&g_mutex);
		sleep(5);
	}
	return NULL;
}

int main() {
	pthread_mutex_init(&g_mutex, NULL); //采用互斥锁的默认属性
	pthread_cond_init(&g_cond, NULL); //采用条件变量的默认属性

	//创建若干个线程
	int i;
	for(int i = 0; i < CONSUMER_COUNT; i++) //创建若干个消费者
		pthread_create(&g_thread[i], NULL, consume, (void*)i);
		//对于这里的i,若是传入指针可能会有竞态问题,因为线程在进入入口函数是,i对应地址值的内容有可能已被
		//主线程所修改,即实际传入所创建线程的值是已经更改过的值,即出现竞态问题,所以还是使用值传递的方式为好;
		//这样采用值传递同样会造成兼容性问题,解决方法是使用一个malloc获取一个int*指针指向的堆内存
		//然后再将该int*内存指针作为参数传递进去,如下所示:
		/*int *p = malloc(sizeof(int));
		*p = i;
		pthread_create(&g_thread[i], NULL, consume, p);*/
	sleep(1);
	for(int i = 0; i < PRODUCER_COUNT; i++) //创建若干个生产者
		pthread_create(&g_thread[i], NULL, produce, (void*)i);

	for(int i = 0; i < PRODUCER_COUNT + CONSUME_COUNT; i++) //回收所有的线程
		pthread_join(g_thread[i], NULL);

	pthread_cond_destory(&g_cond); //销毁条件变量
	pthread_mutex_destory(&g_mutex); //销毁互斥量
	return 0;
}



5. 对条件变量函数的分析

5.1 pthread_cond_wait() 分析

对于以上代码实现当中的如下代码部分,实际上进行了三次操作:

	while(0 == nready) {
		printf("Consumer %d begin wait a condition...\n", num);
		pthread_cond_wait(&g_cond, &g_mutex); //等待条件变量满足
	}
  • 对g_mutex进行解锁,条件是保护区,如果不释放锁则会造成死锁;
    (解锁的条件首先是拥有锁,所以在调用pthread_cond_wait前必须有pthread_mutex_lock操作)
  • 等待条件,直到有线程向其发起通知;
  • 得到通知后,pthread_cond_wait返回前会重新对g_mutex进行加锁操作;

5.1 pthread_cond_signal() 分析

此外,以上代码为什么使用while而不是if。pthread_cond_signal向第一个等待条件的线程发起通知,如果没有任何一个线程处理等待条件的状态,这个通知将被忽略。

在man手册中对于pthread_cond_wait的返回有如下描述:

If a signal is delivered to a thread waiting for a condition variable,upon return from 
the signal handler the thread resumes waiting for the condition 
variable as if it was not interrupted, or it shall return zero due to suprious wakeup.

翻译一下就是,如果一个信号传达给一个正在等待条件变量的线程,或者就像中断没有发生一样线程从信号句柄返回继续等待条件变量,或由于假的唤醒返回0。

在虚假唤醒的情况下,若是使用if则唤醒后pthread_cond_wait返回0,则对共享变量nready的操作是有问题的,因为此时nready有可能为0,是不能够进行消费的,while作为双重保险,以排除虚假唤醒的情形,使得代码更健壮。


5.3 pthread_cond_broadcast() 分析

向所有等待线程发起该通知。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值