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

注意,您需要互斥来保护工作队列。注意,条件变量本身并不知道是否有功。也就是说,条件变量必须与条件相关联,该条件必须由代码维护,并且由于它在线程之间共享,因此必须使用互斥锁进行保护。

其他回答

一个条件变量是非常有限的,如果你只能信号一个条件,通常你需要处理一些数据,相关的条件被信号。信号/唤醒必须在不引入竞态条件的情况下实现,或者过于复杂

出于技术上的原因,pthread也会给你一个虚假的唤醒。这意味着您需要检查一个谓词,这样您就可以确保该条件确实发出了信号——并将其与虚假唤醒区分开来。检查这样一个条件是否等待它需要被保护——所以一个条件变量需要一种方法来原子地等待/唤醒,同时锁定/解锁一个保护该条件的互斥锁。

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

想象一个生产者线程通过'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之前执行

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

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的处理——尽管这将变得复杂,特别是如果您想要许多生产者/消费者。)

因此,在等待/从条件中醒来时,需要一种方法来原子地释放/获取互斥量。这就是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);
}

(生产者自然需要采取同样的预防措施,总是用相同的互斥量保护'some_data',并确保它不会覆盖some_data,如果some_data当前是!= NULL)

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

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

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

注意,您需要互斥来保护工作队列。注意,条件变量本身并不知道是否有功。也就是说,条件变量必须与条件相关联,该条件必须由代码维护,并且由于它在线程之间共享,因此必须使用互斥锁进行保护。

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

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()处理。

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

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并发出信号,但没有线程在等待,因此没有线程 中醒来。当父进程再次运行时,它将永远处于休眠状态,这非常令人震惊。

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