我使用boost :: thread来管理线程。在我的程序中,我有一些线程(工作者),有时会被激活以同时完成某项工作。
现在我使用boost :: condition_variable:并且所有线程都在自己的conditional_variableS对象上调用boost :: condition_variable :: wait()调用。
当我使用conditional_variables时,我可以在经典方案中使用互斥锁吗?我想唤醒线程,但不需要将一些数据传递给它们,因此在唤醒过程中不需要锁定/解锁互斥锁,为什么我要花费CPU(但是,我应该记住虚假的唤醒)?
当CV收到通知时,boost :: condition_variable :: wait()调用尝试REACQUIRE锁定对象。但我不需要这个确切的设施。
从另一个线程中唤醒多个线程的最便宜的方法是什么?
答案 0 :(得分:3)
如果你没有重新获取锁定对象,那么线程如何知道它们已经完成了等待?会告诉他们什么?从块返回告诉他们什么都没有,因为阻塞对象是无状态的。它没有“未锁定”或“不阻塞”状态才能返回。
你必须将一些数据传递给他们,否则在他们不得不等待之前他们怎么知道呢?现在他们不知道呢?条件变量完全是无状态的,因此您需要的任何状态都必须由您维护和传递。
一种常见模式是使用互斥锁,条件变量和状态整数。要阻止,请执行以下操作:
获取互斥锁。
复制状态整数的值。
阻止条件变量,释放互斥锁。
如果状态整数与您处理时的状态整数相同,请转到步骤3.
释放互斥锁。
要取消阻止所有线程,请执行以下操作:
获取互斥锁。
递增状态整数。
广播条件变量。
释放互斥锁。
请注意锁定算法的第4步如何测试线程是否已完成等待?请注意,此代码如何跟踪线程是否已阻止,因为线程决定阻止?你必须这样做,因为条件变量本身不会这样做。 (这就是你需要重新获取锁定对象的原因。)
如果您尝试删除状态整数,则代码将无法预测。有时你会因为错过唤醒而阻塞太长时间,有时你会因为虚假的唤醒而无法阻挡足够长的时间。只有受互斥锁保护的状态整数(或类似谓词)才能告诉线程何时等待以及何时停止等待。
另外,我还没有看到你的代码如何使用它,但它几乎总是折叠成你已经使用的逻辑。为什么线程会阻塞?是因为他们没有工作要做吗?当他们醒来时,他们会弄清楚该做什么吗?好吧,发现他们没有工作要做,并找出他们需要做的工作需要一些锁,因为它是共享状态,对吧?因此,当您决定阻止并且在等待时需要重新获取时,几乎总是存在锁定。
答案 1 :(得分:0)
为了控制执行并行作业的线程,有一个很好的原语叫做屏障。
使用一些正整数值N初始化屏障,表示它保存的线程数。屏障只有一个操作:wait
。当N
个线程调用wait时,屏障会释放所有这些线程。另外,其中一个线程被赋予一个特殊的返回值,表明它是“串行线程”;该线程将是一个做一些特殊工作的线程,比如从其他线程集成计算结果。
限制是给定的障碍必须知道确切的线程数。它非常适合并行处理类型的情况。
POSIX在2003年增加了障碍。网络搜索表明Boost也有这些障碍。
答案 2 :(得分:0)
一般来说,你不能。
假设算法看起来像这样:
ConditionVariable cv;
void WorkerThread()
{
for (;;)
{
cv.wait();
DoWork();
}
}
void MainThread()
{
for (;;)
{
ScheduleWork();
cv.notify_all();
}
}
注意:我故意在此伪代码中省略对互斥锁的任何引用。出于本示例的目的,我们假设ConditionVariable不需要互斥锁。
第一次通过MainTnread(),工作排队,然后它通知WorkerThread()它应该执行它的工作。在这一点上可能会发生两件事:
在#1的情况下,WorkerThread()回来在CV上睡觉,并被下一个cv.notify()唤醒,一切都很顺利。
如果是#2,MainThread()会回来并通知...... nobody 并继续。同时,WorkerThread()最终在其循环中返回并等待CV,但它现在是MainThread()后面的一次或多次迭代。
这被称为“失去的唤醒”。它类似于臭名昭着的“虚假唤醒”,因为两个线程现在对发生了多少notify()s有不同的想法。如果您期望两个线程保持同步(通常是您),则需要某种共享同步原语来控制它。这就是互斥体的用武之地。它有助于避免失去唤醒,这可能是一个比虚假变种更严重的问题。无论哪种方式,效果都可能很严重。
更新:有关此设计背后的进一步说明,请参阅原始POSIX作者之一的评论:https://groups.google.com/d/msg/comp.programming.threads/cpJxTPu3acc/Hw3sbptsY4sJ
虚假唤醒有两件事:
- 仔细编写您的程序,即使您确定它也能正常工作 错过了什么。
- 支持高效的SMP实施
可能有少数情况下“绝对,偏执地正确” 条件唤醒的实现,同时等待和 信号/广播在不同的处理器上,需要额外的 同步会减慢所有条件变量操作 在99.99999%的通话中没有提供任何好处。值得吗? 高架?没办法!
但是,真的,这是一个借口,因为我们想强迫人们去 写安全代码。 (是的,这是事实。)
答案 3 :(得分:0)
boost :: condition_variable :: notify _ *(lock) NOT 要求调用者保持对互斥锁的锁定。这是对Java模型的一个很好的改进,因为它通过持有锁来解除线程的通知。
严格来说,这意味着以下无意义的代码应该按照您的要求进行:
lock_guard lock(mutex);
// Do something
cv.wait(lock);
// Do something else
unique_lock otherLock(mutex);
//do something
otherLock.unlock();
cv.notify_one();
我认为你不需要先调用otherLock.lock()。