为什么在pthread_cond_wait之前需要进行条件检查?

21

我想学习 pthread_cond_wait 的基础知识。在所有的使用情况中,我看到要么

if(cond is false)
   pthread_cond_wait
或者
while(cond is false)
   pthread_cond_wait

我的问题是,我们只想在条件为false时进行cond_wait,那么为什么我要费力地显式放置if/while循环。我可以理解,在cond_wait之前没有任何if/while检查,我们将直接进入该函数并且它不会返回。条件检查是否仅用于解决此目的,还是有其他重要性?如果它用于解决不必要的条件等待,那么放置条件检查并避免使用cond_wait是否类似于轮询?我正在这样使用cond_wait

void* proc_add(void *name){
    struct vars *my_data = (struct vars*)name;
    printf("In thread Addition and my id = %d\n",pthread_self());
    while(1){
    pthread_mutex_lock(&mutexattr);
    while(!my_data->ipt){  // If no input get in
            pthread_cond_wait(&mutexaddr_add,&mutexattr);  // Wait till signalled
            my_data->opt = my_data->a + my_data->b;
            my_data->ipt=1;
            pthread_cond_signal(&mutexaddr_opt);
    }
    pthread_mutex_unlock(&mutexattr);
    if(my_data->end)
            pthread_exit((void *)0);
    }
}

逻辑是,我要求输入线程在有输入时处理数据并通知输出线程将其打印。

2个回答

29

你需要使用while循环,因为调用了pthread_cond_wait的线程可能会在等待的条件未达到时被唤醒。这种现象称为"虚假唤醒"。

这不是一个bug,而是条件变量实现的方式。

在 man 手册中也可以找到相关信息:

pthread_cond_timedwait()pthread_cond_wait() 函数可能产生虚假唤醒。由于从 pthread_cond_timedwait()pthread_cond_wait() 返回并不意味着该谓词的值已被修改,因此应在返回后重新评估该谓词。

有关实际代码的更新:

void* proc_add(void *name) 
{
    struct vars *my_data = (struct vars*)name;

    printf("In thread Addition and my id = %d\n",pthread_self());

    while(1) {

        pthread_mutex_lock(&mutexattr);

        while(!my_data->ipt){  // If no input get in
            pthread_cond_wait(&mutexaddr_add,&mutexattr);  // Wait till signalled
        }

        my_data->opt = my_data->a + my_data->b;
        my_data->ipt=1;
        pthread_cond_signal(&mutexaddr_opt);

        pthread_mutex_unlock(&mutexattr);

        if(my_data->end)
            pthread_exit((void *)0);
        }
    }
}

是的,我尝试过了。但是,在处理输入并向输出线程发出信号后,我必须等待下一个输入。因此,我使用了while循环。在我的情况下,线程必须始终等待输入。因此,我怀疑是否可以通过cond_wait来解决问题,而不需要在实际的cond_wait之前进行if / while检查。 - CHID
1
我已经更新了关于你的代码示例的答案。这就是我会这样做的方式。顺便说一下,用于条件变量的锁定/解锁互斥量的方案有很多不同。这里有一个关于此的好答案 - https://dev59.com/6XE85IYBdhLWcg3wdDSz#2763749 - Maksim Skurydzin
感谢更新,Maxim。最后一次,变量my_data->ipt应始终为0。我不希望出现语句my_data->ipt=1,因为这可能会导致下一次输出线程发出无效信号。最终,每次都应由于while(1)等待cond_wait。那么即使考虑到虚假唤醒,为什么仍应存在while(!my_data->ipt)检查呢? - CHID
1
虚假唤醒解释了为什么你使用 while() 而不是 if(),但它们并不能解释为什么你要使用 if() 来代替对 pthread_cond_wait() 的无条件调用。 - caf
没关系,Maxim。我现在明白了。感谢大家帮助我。 - CHID
显示剩余4条评论

16
在等待之前,必须在互斥锁下测试条件,因为条件变量的信号不会排队(条件变量不是信号量)。也就是说,如果一个线程在该条件变量上调用pthread_cond_wait()时没有线程被阻塞,那么当另一个线程调用pthread_cond_signal()时,这个信号不起作用。
这意味着,如果有一个线程设置了条件:
pthread_mutex_lock(&m);
cond = true;
pthread_cond_signal(&c);
pthread_mutex_unlock(&m);

然后另一个线程无条件等待:

pthread_mutex_lock(&m);
pthread_cond_wait(&c, &m);
/* cond now true */

这个第二个线程将永远阻塞。为了避免这种情况,需要让第二个线程检查条件:

pthread_mutex_lock(&m);
if (!cond)
    pthread_cond_wait(&c, &m);
/* cond now true */

由于在使用互斥锁m时,只有在修改cond的情况下才会发生,因此这意味着第二个线程仅在cond为假时等待。

在健壮的代码中使用while()循环而不是if()的原因是因为pthread_cond_wait()不能保证不会出现虚假唤醒。使用while()也意味着信号量的标记总是安全的 - “额外”的信号不会影响程序的正确性,这意味着您可以将信号移到已解锁的代码块之外。


1
这很清晰易懂。非常感谢@caf。 - CHID

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接