15

我需要同步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 的薄包装。

所以,问题:

  1. notify_one不使用互斥锁保护condition_variable_any或是否是线程安全的condition_variable
  2. 为什么 condition_variable_any 的实现使用额外的互斥锁?
  3. 为什么执行condition_variable_any::notify_onecondition_variable::notify_one不同?也许condition_variable::notify_one需要手动保护,但condition_variable_any::notify_one不需要?它是 libstdc++ 错误吗?
4

2 回答 2

14

即它只是condition_variable+mutex 的薄包装。

呃没有。仅仅因为它具有这些类型的成员并不能使它成为一个薄包装器。尝试了解它实际上做了什么,而不仅仅是它的私有成员的类型。那里有一些非常微妙的代码。

  1. 对于 condition_variable_any 或 condition_variable,不通过互斥锁保护 notify_one 是否是线程安全的?

是的。

实际上,notify_one()在互斥锁锁定的情况下调用会导致等待线程唤醒,尝试锁定互斥锁,发现它仍然被通知线程锁定,然后重新进入休眠状态,直到互斥锁被释放。

如果您在notify_one()没有锁定互斥锁的情况下调用,那么唤醒线程可以立即运行。

2 为什么condition_variable_any 的实现使用额外的互斥锁?

condition_variable_any可以与任何Lockable类型一起使用,不仅是std:mutex,而且在 libstdc++ 中的内部使用 a condition_variable,它只能与 一起使用std::mutex,因此它也有一个内部std::mutex对象。

因此,该condition_variable_any工作与两个互斥锁一起工作,一个由用户提供的外部互斥锁和一个由实现使用的内部互斥锁。

3 为什么 condition_variable_any::notify_one 和 condition_variable::notify_one 的实现不同?也许 condition_variable::notify_one 需要手动保护,但 condition_variable_any::notify_one 不需要?它是 libstdc++ 错误吗?

不,这不是错误。

该标准要求调用wait(mx)必须以原子方式解锁mx和休眠。libstdc++ 使用内部互斥体来提供原子性保证。如果其他线程即将在condition_variable_any.

于 2013-04-09T16:29:28.590 回答
2

(1) 从数据竞争的角度来看,我看不出有任何理由表明条件变量必须由互斥锁保护。显然,您有可能收到冗余通知或丢失通知,但如果这是您的程序可接受或可恢复的错误条件,我不相信标准中有任何内容会使其非法。当然,标准不会保护您免受竞争条件的影响。确保竞争条件是良性的是程序员的责任。(当然,程序员不要放置任何“数据竞争”,这些竞争在标准中非常具体地定义但不直接应用于同步原语,否则会引发未定义的行为,这一点很重要。)

(2) 我无法回答关于标准库设施的内部实现这样的问题。当然,供应商有责任提供正常工作并符合规范的库设施。这个库的实现可能有一些内部状态需要互斥以避免损坏,或者它可能执行锁定以避免丢失或冗余通知。(仅仅因为您的程序可以容忍它们,并不意味着该库的任意用户可以,而且总的来说我希望他们不能。)这只是我的猜测,他们用这个互斥锁保护了什么。

(3)condition_variable_any可用于任何类似锁的对象,而condition_variable专门设计用于与unique_lock<mutex>. 后者可能比前者更容易实现和/或性能更高,因为它明确知道它正在操作哪些类型以及它们需要什么(它们是否微不足道,它们是否适合缓存行,它们是否直接映射到特定平台的系统调用集、栅栏或缓存一致性保证它们所暗示的内容等),而前者提供了一种通用工具,用于对锁定对象进行操作,而不会受到std::mutexor的约束std::unique_lock<>

于 2013-04-08T20:26:30.637 回答