我正在阅读pthread.h;与条件变量相关的函数(如pthread_cond_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.

只要等待返回时有一些可用的工作,就在这个循环中完成。当线程被标记为停止工作时(通常是由另一个线程设置退出条件,然后启动条件变量来唤醒该线程),循环将退出,互斥锁将被解锁,该线程将退出。

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

thread:
    initialise.
    lock mutex.
    while thread not told to stop working:
        wait on condvar using mutex.
        if work is available to be done:
            copy work to thread local storage.
            unlock mutex.
            do the work.
            lock mutex.
    unlock mutex.
    clean up.
    exit thread.

它允许其他消费者在它做功的时候接收功。

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

lock mutex.
flag work as available.
signal condition variable.
unlock mutex.

大多数通常被错误地称为虚假唤醒的情况通常都是因为在pthread_cond_wait调用(广播)中已经发出了多个线程的信号,其中一个线程会返回互斥锁,完成工作,然后重新等待。

然后,当没有工作要做的时候,第二条发出信号的线就可以出来了。所以你必须有一个额外的变量来指示应该做的工作(这是由condvar/互斥锁对固有的互斥锁保护的-其他线程需要在改变互斥锁之前锁定它)。

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

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

其他回答

我没有发现其他的答案像这一页一样简洁易读。通常等待代码看起来像这样:

mutex.lock()
while(!check())
    condition.wait(mutex) # atomically unlocks mutex and sleeps. Calls 
                          # mutex.lock() once the thread wakes up.
mutex.unlock()

将wait()包在互斥锁中有三个原因:

如果没有互斥,另一个线程可能会在wait()之前发出()信号,我们将错过这个唤醒。 通常check()依赖于来自另一个线程的修改,所以无论如何都需要对它进行互斥。 确保优先级最高的线程先进行(互斥锁的队列允许调度器决定谁下一个进行)。

第三点并不总是值得关注的——从文章到对话的历史背景是有联系的。

关于这种机制,经常提到虚假唤醒(即等待线程在没有调用signal()的情况下被唤醒)。但是,这样的事件由循环的check()处理。

如果你想要一个条件变量的真实例子我在课堂上做了一个练习

#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);
}

关于这一点有很多解释,但我想用一个例子来概括一下。

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 }

代码片段有什么问题?在行动前稍作思考。


这个问题真的很微妙。如果父类调用 Thr_parent(),然后检查done的值,它会看到它是0和 因此,去睡觉吧。但就在它呼叫等待睡觉之前,父母 在6-7行之间被中断,并且子程序运行。子进程改变状态变量 Done to 1并发出信号,但没有线程在等待,因此没有线程 中醒来。当父进程再次运行时,它将永远处于休眠状态,这非常令人震惊。

如果它们是在分别获得锁时执行的呢?

当你调用pthread_cond_wait时,互斥锁应该是锁定的;当你调用它时,它会自动地解锁互斥锁,然后在条件上阻塞。一旦条件发出信号,它就会自动锁定它并返回。

这允许在需要的情况下实现可预测的调度,因为发送信号的线程可以等到互斥锁释放后再进行处理,然后发出条件信号。

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

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

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

您通常会看到以下使用条件变量的操作,说明它们是如何工作的。下面的例子是一个工作线程,它通过信号将工作分配给一个条件变量。

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.

只要等待返回时有一些可用的工作,就在这个循环中完成。当线程被标记为停止工作时(通常是由另一个线程设置退出条件,然后启动条件变量来唤醒该线程),循环将退出,互斥锁将被解锁,该线程将退出。

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

thread:
    initialise.
    lock mutex.
    while thread not told to stop working:
        wait on condvar using mutex.
        if work is available to be done:
            copy work to thread local storage.
            unlock mutex.
            do the work.
            lock mutex.
    unlock mutex.
    clean up.
    exit thread.

它允许其他消费者在它做功的时候接收功。

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

lock mutex.
flag work as available.
signal condition variable.
unlock mutex.

大多数通常被错误地称为虚假唤醒的情况通常都是因为在pthread_cond_wait调用(广播)中已经发出了多个线程的信号,其中一个线程会返回互斥锁,完成工作,然后重新等待。

然后,当没有工作要做的时候,第二条发出信号的线就可以出来了。所以你必须有一个额外的变量来指示应该做的工作(这是由condvar/互斥锁对固有的互斥锁保护的-其他线程需要在改变互斥锁之前锁定它)。

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

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