C 为什么pthreads’;条件变量函数需要互斥锁吗?

C 为什么pthreads’;条件变量函数需要互斥锁吗?,c,pthreads,mutex,condition-variable,C,Pthreads,Mutex,Condition Variable,我正在阅读pthread.h;与条件变量相关的函数(如pthread\u cond\u wait(3))需要一个互斥量作为参数。为什么?据我所知,我将创建一个互斥体,用作那个参数?互斥锁应该做什么?这只是条件变量(或最初)的实现方式 互斥锁用于保护条件变量本身。这就是为什么你需要在等待前把它锁上 等待将“自动”解锁互斥锁,允许其他人访问条件变量(用于发送信号)。然后,当向条件变量发送信号或广播时,等待列表上的一个或多个线程将被唤醒,并且该线程的互斥锁将再次被神奇地锁定 您通常会看到以下带有条件变

我正在阅读
pthread.h
;与条件变量相关的函数(如
pthread\u cond\u wait(3)
)需要一个互斥量作为参数。为什么?据我所知,我将创建一个互斥体,用作那个参数?互斥锁应该做什么?

这只是条件变量(或最初)的实现方式

互斥锁用于保护条件变量本身。这就是为什么你需要在等待前把它锁上

等待将“自动”解锁互斥锁,允许其他人访问条件变量(用于发送信号)。然后,当向条件变量发送信号或广播时,等待列表上的一个或多个线程将被唤醒,并且该线程的互斥锁将再次被神奇地锁定

您通常会看到以下带有条件变量的操作,说明它们是如何工作的。下面的示例是一个工作线程,它通过向条件变量发送信号来完成工作

thread:
    initialise.
    lock mutex.
    while thread not told to stop working:
        wait on condvar using mutex.
        if work is available to be done:
            do the work.
    unlock mutex.
    clean up.
    exit thread.
如果等待返回时有一些可用的,则工作在该循环中完成。当线程被标记为停止工作时(通常由另一个线程设置退出条件,然后启动条件变量以唤醒该线程),循环将退出,互斥锁将解锁,该线程将退出

上面的代码是一个单一使用者模型,因为在工作完成时互斥锁保持锁定。对于多消费者变体,您可以使用,例如:

它允许其他消费者在这个消费者工作时接收工作

condition变量减轻了轮询某个条件的负担,而不是允许另一个线程在需要发生某些事情时通知您。另一个线程可以告诉该线程工作可用,如下所示:

lock mutex.
flag work as available.
signal condition variable.
unlock mutex.
通常被错误地称为虚假唤醒的绝大多数情况通常都是因为多个线程在其
pthread\u cond\u wait
调用(广播)中发出了信号,其中一个线程会返回互斥锁,完成工作,然后重新等待

然后,当没有工作要做时,第二个发出信号的线程可能会出现。因此,您必须有一个额外的变量来指示应该完成的工作(这里的condvar/mutex对本质上是受互斥保护的,但是其他线程需要在更改互斥之前锁定互斥)

从技术上讲,线程可以从条件等待中返回,而不会被另一个进程踢出(这是一个真正的虚假唤醒),但是,在我多年从事pthreads的工作中,无论是在代码的开发/服务中,还是作为pthreads的用户,我从未收到过这样的消息。也许这只是因为惠普有一个像样的实现:-)


在任何情况下,处理错误情况的代码也会处理真正的虚假唤醒,因为不会为这些错误设置可用工作标志。

当您调用
pthread\u cond\u wait
时,互斥锁应该被锁定;当您以原子方式调用它时,它都会解锁互斥锁,然后阻塞该条件。一旦条件发出信号,它就会自动再次锁定并返回


这允许在需要时实现可预测的调度,因为执行信号发送的线程可以等待直到释放互斥体来执行其处理,然后向条件发送信号。

如果只能向条件发送信号,则条件变量是非常有限的,通常,您需要处理一些与发出信号的条件相关的数据。信号/唤醒必须以原子方式进行,以在不引入竞争条件的情况下实现,或者过于复杂

由于技术原因,pthreads还可以为您提供。这意味着您需要检查一个谓词,这样您就可以确保实际发出了条件信号——并将其与虚假唤醒区分开来。检查与等待相关的条件需要保护-因此,条件变量需要一种方法,在锁定/解锁保护该条件的互斥锁时,以原子方式等待/唤醒

考虑一个简单的例子,其中通知您生成了一些数据。也许另一个线程生成了一些您想要的数据,并设置了指向该数据的指针

想象一个生产者线程通过“some_data”将一些数据传递给另一个消费者线程 指针

while(1) {
    pthread_cond_wait(&cond); //imagine cond_wait did not have a mutex
    char *data = some_data;
    some_data = NULL;
    handle(data);
}
您自然会得到很多竞争条件,如果另一个线程在您醒来后立即执行
data=new\u data
,但在您执行
data=some\u data

你也不能真正创建自己的互斥锁来保护这种情况

while(1) {

    pthread_cond_wait(&cond); //imagine cond_wait did not have a mutex
    pthread_mutex_lock(&mutex);
    char *data = some_data;
    some_data = NULL;
    pthread_mutex_unlock(&mutex);
    handle(data);
}
不起作用,在唤醒和获取互斥之间仍有可能出现竞争情况。将互斥体放在pthread_cond_wait之前对您没有帮助,就像您现在所做的那样 等待时保持互斥-即制作人将永远无法获取互斥。 (注意,在这种情况下,您可以创建第二个条件变量,以向生产者发出信号,表明您已经处理了
一些\u数据
——尽管这会变得复杂,特别是如果您需要许多生产者/消费者的话。)

因此,您需要一种在等待/从状态中醒来时自动释放/获取互斥体的方法。这就是pthread条件变量的作用,下面是您要做的:

while(1) {
    pthread_mutex_lock(&mutex);
    while(some_data == NULL) { // predicate to acccount for spurious wakeups,would also 
                               // make it robust if there were several consumers
       pthread_cond_wait(&cond,&mutex); //atomically lock/unlock mutex
    }

    char *data = some_data;
    some_data = NULL;
    pthread_mutex_unlock(&mutex);
    handle(data);
}

(生产者自然需要采取同样的预防措施,始终使用相同的互斥锁保护“某些_数据”,并确保在某些_数据当前为!=NULL时不会覆盖某些_数据)

POSIX条件变量是无状态的。所以维护国家是你的责任。由于等待的线程和通知其他线程停止等待的线程都将访问该状态,因此必须使用互斥锁对其进行保护。如果您认为可以在没有互斥的情况下使用条件变量,那么您还没有理解条件变量是无状态的

条件五
while(1) {
    pthread_mutex_lock(&mutex);
    while(some_data == NULL) { // predicate to acccount for spurious wakeups,would also 
                               // make it robust if there were several consumers
       pthread_cond_wait(&cond,&mutex); //atomically lock/unlock mutex
    }

    char *data = some_data;
    some_data = NULL;
    pthread_mutex_unlock(&mutex);
    handle(data);
}
while(1)
{
    pthread_mutex_lock(&work_mutex);

    while (work_queue_empty())       // wait for work
       pthread_cond_wait(&work_cv, &work_mutex);

    work = get_work_from_queue();    // get work

    pthread_mutex_unlock(&work_mutex);

    do_work(work);                   // do that work
}
void AssignWork(WorkItem work)
{
    pthread_mutex_lock(&work_mutex);

    add_work_to_queue(work);           // put work item on queue

    pthread_cond_signal(&work_cv);     // wake worker thread

    pthread_mutex_unlock(&work_mutex);
}
// incorrect usage:
// thread 1:
while (notDone) {
    pthread_mutex_lock(&mutex);
    bool ready = protectedReadyToRunVariable
    pthread_mutex_unlock(&mutex);
    if (ready) {
        doWork();
    } else {
        pthread_cond_wait(&cond1); // invalid syntax: this SHOULD have a mutex
    }
}

// signalling thread
// thread 2:
prepareToRunThread1();
pthread_mutex_lock(&mutex);
   protectedReadyToRuNVariable = true;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond1);

Now, lets look at a particularly nasty interleaving of these operations

pthread_mutex_lock(&mutex);
bool ready = protectedReadyToRunVariable;
pthread_mutex_unlock(&mutex);
                                 pthread_mutex_lock(&mutex);
                                 protectedReadyToRuNVariable = true;
                                 pthread_mutex_unlock(&mutex);
                                 pthread_cond_signal(&cond1);
if (ready) {
pthread_cond_wait(&cond1); // uh o!
// correct usage:
// thread 1:
while (notDone) {
    pthread_mutex_lock(&mutex);
    bool ready = protectedReadyToRunVariable
    if (ready) {
        pthread_mutex_unlock(&mutex);
        doWork();
    } else {
        pthread_cond_wait(&mutex, &cond1);
    }
}

// signalling thread
// thread 2:
prepareToRunThread1();
pthread_mutex_lock(&mutex);
   protectedReadyToRuNVariable = true;
   pthread_cond_signal(&mutex, &cond1);
pthread_mutex_unlock(&mutex);
#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "unistd.h"

int compteur = 0;
pthread_cond_t varCond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex_compteur;

void attenteSeuil(arg)
{
    pthread_mutex_lock(&mutex_compteur);
        while(compteur < 10)
        {
            printf("Compteur : %d<10 so i am waiting...\n", compteur);
            pthread_cond_wait(&varCond, &mutex_compteur);
        }
        printf("I waited nicely and now the compteur = %d\n", compteur);
    pthread_mutex_unlock(&mutex_compteur);
    pthread_exit(NULL);
}

void incrementCompteur(arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex_compteur);

            if(compteur == 10)
            {
                printf("Compteur = 10\n");
                pthread_cond_signal(&varCond);
                pthread_mutex_unlock(&mutex_compteur);
                pthread_exit(NULL);
            }
            else
            {
                printf("Compteur ++\n");
                compteur++;
            }

        pthread_mutex_unlock(&mutex_compteur);
    }
}

int main(int argc, char const *argv[])
{
    int i;
    pthread_t threads[2];

    pthread_mutex_init(&mutex_compteur, NULL);

    pthread_create(&threads[0], NULL, incrementCompteur, NULL);
    pthread_create(&threads[1], NULL, attenteSeuil, NULL);

    pthread_exit(NULL);
}
mutex.lock()
while(!check())
    condition.wait(mutex) # atomically unlocks mutex and sleeps. Calls 
                          # mutex.lock() once the thread wakes up.
mutex.unlock()
1 void thr_child() {
2    done = 1;
3    pthread_cond_signal(&c);
4 }

5 void thr_parent() {
6    if (done == 0)
7        pthread_cond_wait(&c);
8 }