我正在阅读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并发出信号,但没有线程在等待,因此没有线程 中醒来。当父进程再次运行时,它将永远处于休眠状态,这非常令人震惊。
如果它们是在分别获得锁时执行的呢?