条件变量

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
 
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 
struct node {
int n_number;
struct node *n_next;
} *head = NULL;
 
/*[thread_func]*/
static void cleanup_handler(void *arg)
{
    printf("Cleanup handler of second thread.\n");
    free(arg);
    (void)pthread_mutex_unlock(&mtx);
}
static void *thread_func(void *arg)
{
    struct node *p = NULL;
 
    pthread_cleanup_push(cleanup_handler, p);
    while (1) {
    pthread_mutex_lock(&mtx);           //这个mutex主要是用来保证pthread_cond_wait的并发性
    while (head == NULL)   {               //这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线程可能会被虚假唤醒,如果这个时候head != NULL,则不是我们想要的情况。这个时候,应该让线程继续进入pthread_cond_wait
		pthread_cond_wait(&cond, &mtx);         // pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源
                                                //用这个流程是比较清楚的/*block-->unlock-->wait() return-->lock*/
    }
        p = head;
        head = head->n_next;
        printf("Got %d from front of queue\n", p->n_number);
        free(p);
        pthread_mutex_unlock(&mtx);             //临界区数据操作完毕,释放互斥锁
    }
    pthread_cleanup_pop(0);
    return 0;
}
 
int main(void)
{
    pthread_t tid;
    int i;
    struct node *p;
    pthread_create(&tid, NULL, thread_func, NULL);   //子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,而不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大
    /*[tx6-main]*/
    for (i = 0; i < 10; i++) {
        p = malloc(sizeof(struct node));
        p->n_number = i;
        pthread_mutex_lock(&mtx);             //需要操作head这个临界资源,先加锁,
        p->n_next = head;
        head = p;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mtx);           //解锁
        sleep(1);
    }
    printf("thread 1 wanna end the line.So cancel thread 2.\n");
    pthread_cancel(tid);             //关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,退出线程,而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。关于取消点的信息,有兴趣可以google,这里不多说了
    pthread_join(tid, NULL);
    printf("All done -- exiting\n");
    return 0;
}

关于虚假唤醒:

pthread_cond_wait 是 futex 系统调用,属于阻塞型的系统调用,当系统调用被信号中断的时候,会返回 -1,并且把 errno 错误码置为EINTR。很多这种系统调用为了防止被信号中断都会重启系统调用(即再次调用一次这个函数),代码如下:

pid_t r_wait(int *stat_loc)
{
    int retval;
    //wait函数因为被信号中断导致调用失败会返回-1,错误码是EINTR  
   //注意:这里的while循环体是一条空语句
    while(((retval = wait(stat_loc)) == -1 && (errno == EINTR));

    return retval;
}

但是 pthread_cond_wait 用途有点不一样,假设 pthread_cond_wait 函数被信号中断了,在 pthread_cond_wait 返回之后,到重新调用之前,pthread_cond_signal 或
pthread_cond_broadcast 可能已经调用过。一旦错失,可能由于条件信号不再产生,再次调用 pthread_cond_wait 将导致程序无限制地等待下去。为了避免这种情况,宁可虚假唤醒,也不能再次调用pthread_cond_wait,以免陷入无穷的等待中。

除了上面的信号因素外,还存在以下情况:条件满足了发送信号,但等到调用 pthread_cond_wait 的线程得到 CPU 资源时,条件又再次不满足了。

好在无论是哪种情况,醒来之后再次测试条件是否满足就可以解决虚假等待的问题。这就是使用 while 循环来判断条件,而不是使用 if 语句的原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值