虚假唤醒是否会解锁所有等待线程,甚至是不相关的线程?
Does a spurious wake up unblock all waiting threads, even the unrelated ones?
我仍然不熟悉C++中的多线程,我目前正在尝试围绕"虚假唤醒"以及导致它们的原因。我对条件变量、内核信号、futex 等进行了一些挖掘,发现了几个关于"虚假唤醒"发生的原因和方式的罪魁祸首,但仍然有一些我找不到答案......
问:虚假唤醒是否会解锁所有等待/阻塞的线程,甚至是等待完全不相关的通知的线程?或者被阻止的线程是否有单独的等待队列,因此等待另一个通知的线程受到保护?
示例:假设我们有 249 名斯巴达人等待攻击波斯人。他们wait()
让他们的领袖列奥尼达斯(第 250 人(notify_all()
何时进攻。现在,在营地的另一边,有49名受伤的斯巴达人正在等待医生(第50名(notify_one()
,以便他可以治疗每个人。一个虚假的觉醒会解除所有等待的斯巴达人的封锁,包括受伤的人,还是只会影响那些等待战斗的人?等待线程是有两个单独的队列,还是只有一个队列?
如果示例具有误导性,请道歉...我不知道还能怎么解释。
虚假唤醒的原因特定于每个操作系统,此类唤醒的属性也是如此。例如,在 Linux 中,当信号传递到阻塞的线程时,就会发生唤醒。执行信号处理程序后,线程不会再次阻塞,而是从被阻塞的系统调用中接收特殊错误代码(通常EINTR
(。由于信号处理不涉及其他线程,因此它们不会被唤醒。
请注意,虚假唤醒不依赖于要阻止的同步基元或该基元上阻止的线程数。非同步阻止系统调用(如read
或write
(也可能发生这种情况。通常,您必须假设任何阻塞系统调用都可能出于任何原因过早返回,除非像 POSIX 这样的规范保证不会返回(即使这样,也可能存在偏离规范的错误和操作系统细节(。
有些人将多余的通知归因于虚假唤醒,因为处理两者通常是相同的。不过,它们并不相同。与虚假唤醒不同,多余的通知实际上是由另一个线程引起的,并且是对条件变量或 futex 执行通知操作的结果。这只是您在未阻塞的线程设法检查它之前检查唤醒可能变为 false 的条件。
在条件变量的上下文中,虚假唤醒仅从服务员的角度来看。 这意味着等待已退出,但条件不为真;因此,惯用法是:
Thing.lock()
while Thing.state != Play {
Thing.wait()
}
....
Thing.unlock()
此循环的每次迭代(除了一次(都将被视为虚假。 为什么会出现:
- 许多条件被多路复用到单个条件变量上;有时这是合适的,有时它只是懒惰的 等待线程会
- 击败您的线程,并在您有机会拥有它之前更改其状态。
- 不相关的事件, 如 kill(2( 处理这样做是为了保证异步处理程序运行后的一致性。
最重要的是验证是否已满足所需条件,如果没有,则重试或放弃。不重新检查可能很难诊断的状况是一个常见的错误。
作为一个更严肃的例子应该说明:
int q_next(Q *q, int idx) {
/* return the q index succeeding this, with wrap */
if (idx + 1 == q->len) {
return 0
} else {
return idx + 1
}
}
void q_get(Q *q, Item *p) {
Lock(q)
while (q->head == q->tail) {
Wait(q)
}
*p = q->data[q->tail]
if (q_next(q, q->head) == q->tail) {
/* q was full, now has space */
Broadcast(q)
}
q->tail = q_next(q, q->tail)
Unlock(q)
}
void q_put(Q *q, Item *p) {
Lock(q)
while (q_next(q, q->head) == q->tail) {
Wait(q)
}
q->data[q->head] = *p
if (q->head == q->tail) {
/* q was empty, data available */
Broadcast(q)
}
q->head = q_next(q, q->head)
Unlock(q)
}
这是一个多读取器、多写入器队列。 编写器等到队列中有空间,将项目放入,如果队列以前为空,则广播以指示现在有数据。 读者等到队列中有东西,从队列中取出该项目,如果队列以前已满,则广播以指示现在有空间。
请注意,条件变量用于两个条件{未满,不为空}。 这些是边沿触发的条件:仅发出从满和空的转换信号。
Q_get 和 q_put 保护自己免受由 [1] 和 [2] 引起的虚假唤醒,并且您可以轻松地检测代码以显示这种情况发生的频率。
- 学习多线程C++:添加线程不会使执行速度更快,即使它看起来应该
- 为什么线程不接受此输入?
- C++ 中的线程不能使用参数
- ASIO signal_set多个 IO 线程不可靠,具体取决于代码顺序?
- C++线程不检测全局变量更改
- std::线程不是全局变量,但在到达创建它的函数的末尾时不会超出范围?
- 线程不是 std c++ 的成员
- 运行 std::线程不在构造函数中
- 为什么我的线程不在后台运行?
- STD ::线程不会退出
- 特征中的多线程(不使用 OpenMP)
- C++11 线程不以 SDL 结尾
- 线程不在 Linux 上终止,但在 Mac 上终止
- 为什么 omp 并行部分中的线程不会在其部分上划分?
- 多线程不像预期的那样行动
- 使用优化标志进行编译时线程不启动
- 串口读取功能的通信超时和线程不超时
- C 11中的线程不在类成员中
- QT 5.7对于Android Main C 线程不持续运行
- C 11线程不起作用