我正在阅读pthread.h;与条件变量相关的函数(如pthread_cond_wait(3))需要一个互斥量作为参数。为什么?据我所知,我要创建一个互斥量只是为了用作那个参数?互斥锁应该做什么?
当前回答
一个条件变量是非常有限的,如果你只能信号一个条件,通常你需要处理一些数据,相关的条件被信号。信号/唤醒必须在不引入竞态条件的情况下实现,或者过于复杂
出于技术上的原因,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)
其他回答
我没有发现其他的答案像这一页一样简洁易读。通常等待代码看起来像这样:
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);
}
并不是所有的条件变量函数都需要互斥:只有等待操作需要。信号和广播操作不需要互斥。条件变量也不会永久地与特定的互斥锁相关联;外部互斥不保护条件变量。如果一个条件变量具有内部状态,例如等待线程的队列,则必须由条件变量中的内部锁保护。
等待操作将一个条件变量和一个互斥锁结合在一起,因为:
一个线程已经锁定了互斥锁,计算了共享变量的某个表达式,发现它为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.
条件变量等待函数通过确保调用线程在放弃互斥量之前被注册为可靠地捕获唤醒来避免丢失唤醒。如果条件变量等待函数不接受互斥量作为参数,这是不可能的。
这似乎是一个具体的设计决策,而不是概念上的需求。
根据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.
关于这一点有很多解释,但我想用一个例子来概括一下。
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并发出信号,但没有线程在等待,因此没有线程 中醒来。当父进程再次运行时,它将永远处于休眠状态,这非常令人震惊。
如果它们是在分别获得锁时执行的呢?