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

我正在阅读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的工作中,无论是在代码的开发/服务中,还是作为用户其中,我从来没有收到过这些。 也许这只是因为惠普有一个体面的实施:-)

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


如果你只能发出一个条件,一个条件变量是相当有限的,通常你需要处理一些与被发送的条件有关的数据。 信号/唤醒必须以原子方式完成,而不会引入竞争条件或过于复杂

由于技术上的原因,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);
}

你自然会得到很多竞争条件,如果其他线程在你醒来之后做了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)


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

注意你需要互斥体来保护工作队列。 注意条件变量本身不知道是否有工作。 也就是说,一个条件变量必须与一个条件相关联,该条件必须由您的代码维护,并且由于它在线程之间共享,所以它必须受互斥体保护。

链接地址: http://www.djcxy.com/p/72177.html

上一篇: Why do pthreads’ condition variable functions require a mutex?

下一篇: How do I return null if my string has no null terminator