linux线程——如何理解pthread_cond_wait和pthread_cond_signal

关于pthread_cond_wait()函数的理解  

        int  pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

       函数传入的参数mutex用于保护条件,因为我们在调用pthread_cond_wait时,如果条件不成立我们就进入阻塞,但是进入阻塞这个期间,如果条件变量改变了的话,那我们就漏掉了这个条件。因为这个线程还没有放到等待队列上,所以调用pthread_cond_wait前要先锁互斥量,即调用pthread_mutex_lock(),pthread_cond_wait在把线程放进阻塞队列后,自动对mutex进行解锁,使得其它线程可以获得加锁的权利。这样其它线程才能对临界资源进行访问并在适当的时候唤醒这个阻塞的进程。当pthread_cond_wait返回的时候又自动给mutex加锁。


实际上边代码的加解锁过程如下:
/************pthread_cond_wait()的使用方法**********/
pthread_mutex_lock(&qlock); /*lock*/
(排入等待队列)pthread_cond_wait(&qready, &qlock); /*block-->unlock-->wait()||(唤醒之后)return-->lock*/
pthread_mutex_unlock(&qlock); /*unlock*/
/*****************************************************/


关于pthread_cond_signal函数:

          int pthread_cond_signal(pthread_cond_t *cond);

          pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
          但使用pthread_cond_signal不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个 pthread_cond_signal调用最多发信一次。

另外,互斥量的作用一般是用于对某个资源进行互斥性的存取,很多时候是用来保证操作是一个原子性的操作,是不可中断的。

            pthread_cond_signal函数与条件变量的典型应用就是用来实现producer/consumer模型。


详细见下面的程序:

#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
struct msg {//产品队列
	struct msg *next;
	int num;
};
volatile struct msg *head;//定义一个volatile变量
pthread_cond_t hasProduct = PTHREAD_COND_INITIALIZER;//静态初始化条件变量
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//静态分配普通š锁
void *consumer(void *p)//消费者函数
{
	struct msg *mp;
	for(;;) 
	{
		pthread_mutex_lock(&lock);//先锁定
		while (head==NULL) {
			/*if (head==NULL) {*/
			//条件受互斥锁保护?
			//用while不用if?
			printf("consumer entering wait......\n");
			pthread_cond_wait(&hasProduct,&lock);//block->unlock->wait||(唤醒以后)return->lock
			printf("consumer leaveing wait : %d......\n",head->num);//这里return以后
		}
		mp = head;
		head = mp->next;
		printf("Consume %d\n",mp->num);
		pthread_mutex_unlock(&lock);//这里最后又会加锁
		free(mp);
		sleep(rand()%5);
	}
}

void *producer(void *p)//生产者
{
	struct msg *mp;//指向产品的指针
	for(;;) {
		mp = malloc(sizeof(struct msg));//先新建一个产品用指针返回
		mp->num = rand()%1000 + 1;
		pthread_mutex_lock(&lock);
		mp->next = head;
		head = mp;
		printf("Produce %d\n",mp->num);
		pthread_mutex_unlock(&lock);
		pthread_cond_signal(&hasProduct);
		//signal与lock位置无关
		sleep(rand()%5);
	}
}
int main(int argc, char *argv[])
{
	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);
	struct msg* mp = head;
	while (head!=NULL) {
		mp = head;
		head = head->next;
		printf("main thread: %d\n",mp->num);
		free(mp);
	}
	return 0;
}


上例运行效果没有问题,但有些疑问还没搞清楚。

wait函数并不是单使该线程进入休眠。

ß wait函数做以下三步的操作:

ß 1.释放Mutex 2.阻塞等待(不耗费cpu周期) 3.当被唤醒时,重新锁定互斥锁并重新测试条件是否满足 。

ß 需要重新测试条件的原因是因为可能存在多个consumer的情况,即如果线程不止两个而是多个,即使线程被唤醒了,如果一个consumer取走了列表里的产品,那么另外一个consumer需要重新进入休眠等待。这也是为什么用while不用if去判断的原因。条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出。从wait或者timewait调用成功返回时,线程需要重新计算条件,因为其他的线程可能已经在运行并改变了条件。

wait函数会在 休眠等待之前释放锁,因此producer是不用担心一直获取不到锁的。

传递给wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。这就是互斥量和条件一起使用的原因,如果consumer判断到列表为空,正准备进入休眠,而此时producer产生节点放到了列表里,consumer却接着进入休眠很明显不是我们想要的,如果producer在其休眠前发出了信号,又会造成没有接受者的情形(一会会讲到这个)。wait返回时,互斥量再次被锁住。

要注意,一定要在改变条件之后再给线程发信号。否则无意义的唤醒休眠线程就违反我们最初引入条件变量的目的了

关于signal与mutex的相对位置,《UNIX高级环境编程》里是说都可以的,只要在cond_signal之后能从列表里取出节点。因为是while循环检查条件,所以不会存在问题:线程醒来,发现队列为空,然后返回继续等待。如果代码不能容忍这种竞争,就需要在向线程发送信号的时候占有互斥量。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值