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


当前回答

并不是所有的条件变量函数都需要互斥:只有等待操作需要。信号和广播操作不需要互斥。条件变量也不会永久地与特定的互斥锁相关联;外部互斥不保护条件变量。如果一个条件变量具有内部状态,例如等待线程的队列,则必须由条件变量中的内部锁保护。

等待操作将一个条件变量和一个互斥锁结合在一起,因为:

一个线程已经锁定了互斥锁,计算了共享变量的某个表达式,发现它为false,因此需要等待。 线程必须自动地从拥有互斥量转移到等待条件。

出于这个原因,等待操作将互斥量和条件作为参数:这样它就可以管理线程从拥有互斥量到等待的原子转移,这样线程就不会成为丢失唤醒竞争条件的受害者。

A lost wakeup race condition will occur if a thread gives up a mutex, and then waits on a stateless synchronization object, but in a way which is not atomic: there exists a window of time when the thread no longer has the lock, and has not yet begun waiting on the object. During this window, another thread can come in, make the awaited condition true, signal the stateless synchronization and then disappear. The stateless object doesn't remember that it was signaled (it is stateless). So then the original thread goes to sleep on the stateless synchronization object, and does not wake up, even though the condition it needs has already become true: lost wakeup.

条件变量等待函数通过确保调用线程在放弃互斥量之前被注册为可靠地捕获唤醒来避免丢失唤醒。如果条件变量等待函数不接受互斥量作为参数,这是不可能的。

其他回答

条件变量与互斥锁相关联,因为这是互斥锁避免竞争的唯一方法。

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

此时,没有线程会向条件变量发出信号,因此thread1将永远等待,即使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);

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

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

并不是所有的条件变量函数都需要互斥:只有等待操作需要。信号和广播操作不需要互斥。条件变量也不会永久地与特定的互斥锁相关联;外部互斥不保护条件变量。如果一个条件变量具有内部状态,例如等待线程的队列,则必须由条件变量中的内部锁保护。

等待操作将一个条件变量和一个互斥锁结合在一起,因为:

一个线程已经锁定了互斥锁,计算了共享变量的某个表达式,发现它为false,因此需要等待。 线程必须自动地从拥有互斥量转移到等待条件。

出于这个原因,等待操作将互斥量和条件作为参数:这样它就可以管理线程从拥有互斥量到等待的原子转移,这样线程就不会成为丢失唤醒竞争条件的受害者。

A lost wakeup race condition will occur if a thread gives up a mutex, and then waits on a stateless synchronization object, but in a way which is not atomic: there exists a window of time when the thread no longer has the lock, and has not yet begun waiting on the object. During this window, another thread can come in, make the awaited condition true, signal the stateless synchronization and then disappear. The stateless object doesn't remember that it was signaled (it is stateless). So then the original thread goes to sleep on the stateless synchronization object, and does not wake up, even though the condition it needs has already become true: lost wakeup.

条件变量等待函数通过确保调用线程在放弃互斥量之前被注册为可靠地捕获唤醒来避免丢失唤醒。如果条件变量等待函数不接受互斥量作为参数,这是不可能的。

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

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

这似乎是一个具体的设计决策,而不是概念上的需求。

根据pthreads文档的说法,互斥锁没有被分离的原因是将它们组合在一起可以显著提高性能,并且由于通用的竞争条件,如果你不使用互斥锁,几乎总是会这样做。

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

Features of Mutexes and Condition Variables It had been suggested that the mutex acquisition and release be decoupled from condition wait. This was rejected because it is the combined nature of the operation that, in fact, facilitates realtime implementations. Those implementations can atomically move a high-priority thread between the condition variable and the mutex in a manner that is transparent to the caller. This can prevent extra context switches and provide more deterministic acquisition of a mutex when the waiting thread is signaled. Thus, fairness and priority issues can be dealt with directly by the scheduling discipline. Furthermore, the current condition wait operation matches existing practice.