C++11 std::condition_variable:我们可以将锁直接传递给通知的线程吗?
C++11 std::condition_variable: can we pass our lock directly to the notified thread?
我正在学习 C++11 并发性,我之前唯一使用并发原语的经验是六年前在操作系统课上,所以如果可以的话,请保持温和。
在 C++11 中,我们可以写
std::mutex m;
std::condition_variable cv;
std::queue<int> q;
void producer_thread() {
std::unique_lock<std::mutex> lock(m);
q.push(42);
cv.notify_one();
}
void consumer_thread() {
std::unique_lock<std::mutex> lock(m);
while (q.empty()) {
cv.wait(lock);
}
q.pop();
}
这工作正常,但我对需要将cv.wait
包装在循环中感到冒犯。我们需要循环的原因对我来说很清楚:
Consumer (inside wait()) Producer Vulture
release the lock
sleep until notified
acquire the lock
I MADE YOU A COOKIE
notify Consumer
release the lock
acquire the lock
NOM NOM NOM
release the lock
acquire the lock
return from wait()
HEY WHERE'S MY COOKIE I EATED IT
现在,我相信unique_lock
的一个很酷的事情是我们可以传递它,对吧?因此,如果我们能这样做,那将是非常优雅的:
Consumer (inside wait()) Producer
release the lock
sleep until notified
acquire the lock
I MADE YOU A COOKIE
notify and yield(passing the lock)
wake(receiving the lock)
return from wait()
YUM
release the lock
现在秃鹫线程无法猛扑,因为互斥锁从I MADE YOU A COOKIE
一直锁定到YUM
。另外,如果notify()
要求你传递锁,这是确保人们在调用notify()
之前实际锁定互斥锁的好方法(请参阅向条件变量发出信号(pthreads))。
我很确定 C++11 没有任何这个成语的标准实现。这有什么历史原因(只是 pthreads 没有这样做吗?然后为什么会这样)?是否有技术原因导致一个冒险的C++程序员无法在标准 C++11 中实现这个成语,称之为可能my_better_condition_variable
?
我也有一种模糊的感觉,也许我正在重新发明信号量,但我在学校里记得的还不够多,不知道这是否准确。
最终的答案是因为 pthreads 没有这样做。 C++是一种封装操作系统功能的语言。 C++不是操作系统或平台。 因此,它封装了Linux,Unix和Windows等操作系统的现有功能。
然而,pthreads 也为这种行为提供了很好的理由。 从开放组基本规格:
其效果是多个线程可以从其调用 pthread_cond_wait() 或 pthread_cond_timedwait() 作为 1 的结果 调用 pthread_cond_signal()。这种效应称为"虚假的" 醒醒"。请注意,这种情况是自我纠正的,因为数字 如此觉醒的线是有限的;例如,下一个 在上述事件序列之后调用 pthread_cond_wait() 的线程 块。
虽然这个问题可以解决,但效率的损失 很少发生的边缘疾病是不可接受的,尤其是 鉴于必须检查与条件相关的谓词 无论如何都是可变的。纠正此问题将不必要地减少 此基本构建块中所有人的并发程度 更高级别的同步操作。
允许虚假唤醒的另一个好处是应用程序 被迫围绕条件等待编写谓词测试循环。 这也使应用程序能够容忍多余的条件 可能编码的相同条件变量上的广播或信号 在应用程序的其他部分。由此产生的应用程序是 因此更坚固。因此,IEEE Std 1003.1-2001 明确记录了 可能会发生虚假唤醒。
所以基本上声称你可以相当容易地在pthreads条件变量(或std::condition_variable
)之上构建my_better_condition_variable
,而不会对性能造成影响。 但是,如果我们my_better_condition_variable
放在基础级别,那么那些不需要my_better_condition_variable
功能的客户无论如何都必须为此付费。
这种将最快、最原始的设计放在堆栈底部的理念,目的是可以在它们之上构建更好/更慢的东西,贯穿整个C++库。 如果C++库未能遵循这一理念,客户经常(并且正确地)感到恼火。
如果你不想编写循环,你可以使用采用谓词的重载:
cv.wait(lock, [&q]{ return !q.is_empty(); });
它被定义为等效于循环,因此它的工作方式与原始代码相同。
即使你可以这样做,C++11规范也允许cv.wait()
虚假地解锁(以考虑具有该行为的平台)。因此,即使没有秃鹫线程(撇开关于它们是否应该存在的争论),消费者线程也不能指望有一个cookie在等待,仍然必须检查。
我认为这不安全:
void producer_thread() {
std::unique_lock<std::mutex> lock(m);
q.push(42);
cv.notify_one();
}
当您通知另一个线程等待锁定时,您仍然持有锁。 因此,可能是另一个线程立即唤醒并尝试在释放锁后调用的析构函数之前获取锁cv.notify_one()
。 这意味着另一个线程最终会返回以永远等待。
所以我认为这应该编码为:
void producer_thread() {
std::unique_lock<std::mutex> lock(m);
q.push(42);
lock.unlock();
cv.notify_one();
}
或者如果您不喜欢手动解锁
void producer_thread() {
{
std::unique_lock<std::mutex> lock(m);
q.push(42);
}
cv.notify_one();
}
- 是否有必要获取锁并在不需要唤醒线程时通知condition_variable?
- C++ Poco - 如何向特定线程发送通知/消息?
- C++ - 如何不错过来自多个线程的多个通知?
- C++ - std::condition_variable 由不同的线程通知
- 线程启动延迟 - 通知所有未唤醒所有线程
- 使用 condition_variable::notify_all 通知多个线程
- 通知所有在多线程C++不起作用.导致死锁
- 通知线程是否始终需要在修改期间锁定共享数据
- 当子线程退出时,如何通知父线程
- QSocketNotifier:不能从另一个线程启用或禁用套接字通知程序
- 使用Berkeley套接字的线程通知
- 通知有关变量变化的线程(信号?)
- 用信号通知线程启动特定的函数
- 线程退出时通知Waiters
- C++11 std::condition_variable:我们可以将锁直接传递给通知的线程吗?
- 通知线程退出
- std::condition_variable - 等待多个线程通知观察者
- 线程之间的更改通知标志需要内存屏障吗
- 视觉提升 条件变量中的同步队列C++不通知其他线程上的等待类方法
- c++主线程通知线程通知主线程