我是否需要同步std::condition_variable/condition_variable_any::notify_one
?
据我所知,如果通知丢失是可以接受的 - 可以调用notify_one
不受保护(例如通过互斥锁)。
例如,我看到了以下使用模式(抱歉,不记得在哪里):
{
{
lock_guard<mutex> l(m);
// do work
}
c.notify_one();
}
但是,我检查了libstdc ++来源,我看到了:
condition_variable::notify_one
void condition_variable::notify_one() noexcept
{
int __e = __gthread_cond_signal(&_M_cond);
// XXX not in spec
// EINVAL
if (__e)
__throw_system_error(__e);
}
和condition_variable_any::notify_one:
void condition_variable_any::notify_one() noexcept
{
lock_guard<mutex> __lock(_M_mutex);
_M_cond.notify_one();
}
这里是condition_variable_any的布局:
class condition_variable_any
{
condition_variable _M_cond;
mutex _M_mutex;
// data end
即。它只是condition_variable + mutex周围的薄包装。
所以,问题:
notify_one
或condition_variable_any
的互斥锁不保护condition_variable
是否可以线程安全?condition_variable_any::notify_one
和condition_variable::notify_one
的实施方式有所不同?也许condition_variable::notify_one
需要手动保护,但condition_variable_any::notify_one
不需要手动保护?是libstdc ++ bug吗?答案 0 :(得分:14)
即。它只是condition_variable + mutex周围的薄包装。
呃,没有。仅仅因为它具有这些类型的成员并不能使它成为一个薄的包装器。试着了解它的实际功能,而不仅仅是其私有成员的类型。那里有一些非常微妙的代码。
- 对于condition_variable_any或condition_variable,不通过互斥锁保护notify_one是否可以线程安全?
醇>
是
实际上,在锁定互斥锁的情况下调用notify_one()
将导致等待线程被唤醒,尝试锁定互斥锁,发现它仍然被通知线程锁定,然后返回睡眠直到互斥锁被释放。
如果在没有锁定互斥锁的情况下调用notify_one()
,则唤醒线程可以立即运行。
2为什么condition_variable_any的实现使用额外的互斥锁?
condition_variable_any
可以与任何可锁定的类型一起使用,而不仅仅是std:mutex
,但在libstdc ++中的内部使用condition_variable
,只能使用std::mutex
使用std::mutex
,因此它也有一个内部condition_variable_any
对象。
因此wait(mx)
可以使用两个互斥锁,即用户提供的外部互斥锁和实现使用的内部互斥锁。
3为什么condition_variable_any :: notify_one和condition_variable :: notify_one的实现不同?也许condition_variable :: notify_one需要手动保护但是condition_variable_any :: notify_one不需要?它是libstdc ++ bug吗?
不,这不是错误。
该标准要求调用mx
必须原子解锁condition_variable_any
并睡眠。 libstdc ++使用内部互斥锁来提供原子性保证。如果其他线程即将等待{{1}},则必须锁定内部互斥锁以避免错过通知。
答案 1 :(得分:2)
(1)我认为,从数据竞争的角度来看,必须用互斥锁来保护发送条件变量的任何理由。显然,您可能会收到冗余通知或丢失通知,但如果这是您的程序可接受或可恢复的错误条件,我不相信标准中的任何内容会使其成为非法。当然,标准不会保护你免受竞争条件的影响;程序员有责任确保竞争条件是良性的。 (当然,程序员必须不进行任何“数据竞赛”,这些竞赛在标准中非常具体地定义,但不直接应用于同步原语,或者召唤未定义的行为。)
(2)我无法回答有关标准库设施内部实施的问题。当然,供应商有责任提供能够正常工作并符合规范的库设施。此库的实现可能具有一些需要互斥以避免损坏的内部状态,或者它可能执行锁定以避免丢失或冗余通知。 (仅仅因为你的程序可以容忍它们,并不意味着库的任意用户都可以,而且一般来说我希望它们不能。)这只是猜测我们用这个互斥锁保护它们。
(3)condition_variable_any
适用于任何类似锁定的对象,而condition_variable
专门用于处理unique_lock<mutex>
。后者可能比前者更容易实现和/或更高性能,因为它具体知道它正在操作哪些类型以及它们需要什么(它们是否是微不足道的,它们是否适合高速缓存行,它们是否直接映射到特定平台的一组系统调用,它们所暗示的围栏或缓存一致性等等,而前者提供了一个通用工具,用于对锁定对象进行操作,而不会特别限制std::mutex
或{{std::unique_lock<>
1}}。