为什么pthreads'的条件变量函数需要一个互斥?为什么pthreads'的条件变

2019-05-10 10:40发布

我读了关于pthread.h ; 条件变量相关的功能(如pthread_cond_wait(3)需要互斥作为参数。 为什么? 至于我可以告诉大家,我将要创建一个互斥体只是作为参数使用? 什么是互斥应该怎么做?

Answer 1:

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

互斥锁是用来保护条件变量本身 。 这就是为什么你需要你做一个等待之前锁定。

等待将“原子”解锁互斥,从而允许其他人访问的条件变量(对于信令)。 然后,当条件变量获得信号,或者广播到一个或多个在轮候名单上的线程将被唤醒并互斥量将再次奇迹般地锁定该线程。

你通常会看到下面的操作使用条件变量,说明他们是如何工作的。 下面的例子是其经由一个信号给条件变量给定的工作工作线程。

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 /互斥对固有互斥保护的 - 但改变它之前锁定互斥所需的其他线程)。

技术上可行的线程从条件等待而不被其他进程被踢回(这是一个真正的虚假唤醒),但是,在我所有的多年工作的并行线程,无论是在代码的开发/服务,并为用户他们的,我从来没有一次收到的其中之一。 也许这只是因为惠普有一个体面的实施:-)

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



Answer 2:

条件变量是相当有限的,如果你只能信号的条件,通常需要来处理的有关向条件是信号的一些数据。 信令/唤醒必须在关于自动完成的实现,没有引入竞争条件,或过于复杂

并行线程也可以给你,为技术性比较强的原因, 虚假唤醒 。 这意味着你需要检查一个谓语,所以你可以肯定的条件实际上是发出信号 - 和区分从虚假唤醒。 检查等方面的条件,以等待它需要被守卫 - 这样一个条件变量需要一种方式来等待原子/唤醒,同时锁定/解锁互斥守着这一条件。

举一个简单的例子,你会收到通知,有一些数据产生的。 也许另一个线程做了一些数据,你想要的,并设置一个指向数据。

想象一下,一个生产者线程通过“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);
}

你会自然得到了很多的竞争条件,如果其他线程做some_data = new_data你有醒来后的权利,但你之前做过data = some_data

你真的不能创建自己的互斥体,以两个后卫这种情况下.eg

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之前放置互斥不帮你,因为你现在将持有的互斥体在等待 - 即生产者将永远无法抓住互斥。 (注意,在这种情况下,你可以创建第二个条件变量的信号,你与做制片some_data -尽管这会变得非常复杂,尤其如果你想许多生产者/消费者)。

因此,你需要一种方式等待/从状态唤醒时,原子释放/抢互斥。 这就是并行线程条件变量呢,这里是你会怎么做:

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

(生产者自然会需要采取同样的措施,始终守着“some_data”用相同的互斥体,并确保如果some_data目前它不会覆盖some_data!= NULL)



Answer 3:

POSIX条件变量是无状态的。 因此,它是你的责任,以保持状态。 由于国家将由两个线程等待,告诉其他线程停止等待线程访问,它必须由一个互斥保护。 如果你认为你可以使用条件变量不互斥,那么你有没有掌握了条件变量是无状态的。

条件变量周围的条件建造的。 线程等待条件变量正在等待一些条件。 该信号的条件变量的线程改变这种状况。 例如,一个线程可能会等待一些数据的到来。 其他某些线程可能会注意到,数据已经到达。 “已收到数据”是条件。

这里是典型的使用条件变量,简化:

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

请注意,您所需要的互斥体保护工作队列。 请注意,条件变量本身不知道是否有工作或没有。 也就是说,条件变量必须与条件相关联时,这个条件必须由你的代码来维持,因为它的线程之间共享,它必须由一个互斥保护。



Answer 4:

并非所有条件变量函数需要一个互斥体:只有等待操作做。 信号和广播业务不需要互斥。 条件变量还没有永久地与一个特定的互斥相关联; 外部互斥不保护条件变量。 如果条件变量具有内部状态,如等待的线程队列,这必须由条件变量内的内部锁保护。

等待操作汇集条件变量和一个互斥体,这是因为:

  • 一个线程已锁定互斥,评价过的共享变量的一些表达,发现它是假的,使得它需要等待。
  • 线程必须从原子拥有互斥体,以等待条件移动。

出于这个原因,等待操作需要作为参数互斥和条件都:所以,它可以从拥有互斥等待管理线程的原子传输,从而使线程不牺牲品失去醒来的竞争条件

存在的当线程不再具有锁定时间窗口,并具有:如果一个线程放弃一个互斥体,然后等待一个无状态的同步对象,但在某种程度上这是不是原子将发生丢失唤醒竞争条件还没开始等待的对象。 在这个窗口中,另一个线程可以进来,让期待已久的条件为真,信号无状态同步,然后消失。 无状态的对象不记得它是信号(它是无状态的)。 所以,那么原来的线程去无状态同步对象上睡觉,没有醒来,即使它需要的条件已经成为现实:丢失唤醒。

条件变量等待功能避免丢失唤醒通过确保调用线程注册到可靠地捕捉唤醒它放弃互斥之前。 这将是不可能的,如果条件变量等待功能没有采取互斥体作为参数。



Answer 5:

我不觉得其他的答案是为简明易读的这个页面 。 通常情况下,等待代码看起来是这样的:

mutex.lock()
while(!check())
    condition.wait()
mutex.unlock()

有三个理由来包装wait()一个互斥体:

  1. 没有互斥另一个线程可能signal()在之前wait()我们会错过这个唤醒。
  2. 一般check()是依赖于从另一个线程修改,所以你需要在它互斥反正。
  3. 以确保最高优先级的线程进行第一个(互斥体队列允许调度来决定谁去下一个)。

第三点是不总是一个问题-历史背景从文章链接的这次谈话 。

虚假的唤醒,往往对于这种机制提及(即等待线程没有苏醒signal()被调用)。 然而,这样的事件是由环形处理check()



Answer 6:

因为它是可避免,这是为了避免在比赛的唯一途径条件变量与互斥有关。

// 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!

在这一点上,没有线程这是会发出信号的条件变量,所以线程1会一直等下去,即使protectedReadyToRunVariable说,它已经准备好去!

解决这个问题的唯一办法是条件变量可以以原子方式释放互斥量,同时开始等待条件变量。 这就是为什么cond_wait函数需要一个互斥体

// 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);


Answer 7:

互斥体应该当你调用被锁定pthread_cond_wait ; 当你调用它,它都以原子上的解锁条件互斥,然后阻止。 一旦条件获得信号,它再次原子,并返回其锁定。

这使得如果需要的话,在那个会做信令可以等到互斥体的线程被释放做了处理,然后信号的条件可预测调度的实现。



Answer 8:

我在做一个类exercice如果你想条件变量的一个真实的例子:

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


Answer 9:

这似乎是一个特定的设计决策,而不是概念上的需要。

每个并行线程文档的互斥体不分离的原因是,有通过结合他们显著的性能提升,他们想到的是,因为共同的竞争条件,如果你不使用互斥体,它几乎总是会无论如何都要完成。

https://linux.die.net/man/3/pthread_cond_wait

互斥和条件变量的特性

有人建议互斥量采集和发布,从条件等待脱钩。 这被拒绝,因为它是,事实上,有利于实现实时操作的综合性质。 这些实施方式可以以原子移动条件变量和以这样的方式该互斥是透明的呼叫者之间的高优先级的线程。 这可以防止不必要的上下文切换,当等待线程发出信号提供了一个互斥体的更多确定性的收购。 因此,公平和优先问题,可以直接由调度纪律处理。 此外,在当前的状态等待操作与现存的实践。



文章来源: Why do pthreads’ condition variable functions require a mutex?