我是否需要同步std :: condition_variable / condition_variable_any :: notify_one

时间:2013-04-08 19:30:02

标签: c++ multithreading c++11 thread-safety condition-variable

我是否需要同步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_onecondition_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 ++ bug吗?

2 个答案:

答案 0 :(得分:14)

  

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

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

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

实际上,在锁定互斥锁的情况下调用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}}。