3 回答
TA贡献1891条经验 获得超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 / mutex对进行互斥保护 - 但是在更改之前需要锁定互斥锁的其他线程)。
这是技术上是可行的一个线程从条件等待而不被其他进程被踢回(这是一个真正的虚假唤醒),但是,在我所有的多年工作的并行线程,无论是在代码的开发/服务,并为用户其中,我从来没有收到其中的一个。也许那只是因为惠普有一个不错的实施:-)
在任何情况下,处理错误情况的相同代码也处理真正的虚假唤醒,因为不会为那些设置工作可用标志。
TA贡献1797条经验 获得超4个赞
如果您只能发出条件信号,则条件变量非常有限,通常您需要处理与发出信号的条件相关的一些数据。信号/唤醒必须以原子方式完成,以实现不引入竞争条件或过于复杂的情况
由于技术原因,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)
TA贡献1810条经验 获得超4个赞
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);}
请注意,您需要使用互斥锁来保护工作队列。请注意,条件变量本身不知道是否有工作。也就是说,条件变量必须与条件相关联,该条件必须由代码维护,并且由于它在线程之间共享,因此必须由互斥锁保护。
- 3 回答
- 0 关注
- 631 浏览
添加回答
举报