2013-04-08 74 views
13

我需要同步std::condition_variable/condition_variable_any::notify_one我需要同步的std :: condition_variable/condition_variable_any :: notify_one

至于我可以看到,如果丢失的通知是可以接受的 - 这是确定调用notify_one不受保护(mutex所为例)。

举例来说,我看到了下面的使用模式(抱歉,不记得在哪里):

{ 
    { 
     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); 
} 

and 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. 它是线程安全的,不会被用于互斥或者condition_variable_anycondition_variable保护notify_one
  2. 为什么condition_variable_any的实现使用额外的互斥量?
  3. 为什么实施condition_variable_any::notify_onecondition_variable::notify_one有所不同?可能condition_variable::notify_one需要手动保护,但condition_variable_any::notify_one不需要?它是libstdC++的错误?

回答

13

I.e.它只是在condition_variable + mutex周围的薄包装。

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

  1. 它是线程安全的,不会被用于互斥或者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与两个互斥体一起工作,外部互斥体由用户提供,而内部的互斥体由实现使用。

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,则必须锁定内部互斥锁以避免错过通知。

+0

谢谢!这帮助了我很多。特别回答2和3 - 关于原子性的好处,以及无论如何都使用互斥体的内部元素。顺便说一句,您可以添加指向pthread_cond_wait http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_cond_wait.html的链接,以显示最常见的实现仅适用于内部中它自己的互斥体。你能否澄清一下你的意思是“这里有一些非常微妙的代码”。 – qble 2013-04-09 20:31:33

+0

关于1. - 可否锁定condition_variable :: notify_one导致错过通知?即线程#1在互斥体中执行任务,发送结果,解锁互斥体,[同时]线程#2锁定互斥体但尚未调用wait,[同时]线程#1调用notify_one,[同时]线程#2调用wait - 通知丢失。 – qble 2013-04-09 20:37:04

+0

这不是一个丢失的通知,这是等待线程在等待之前不检查条件谓词。锁定互斥锁并不能解决这种情况,通知仍可能在等待线程锁定互斥锁之前发生。您**必须在等待条件变量时检查关联的谓词 – 2013-04-10 00:27:24

2

(1)从数据竞赛的角度看,我没有看到任何信号条件变量的原因必须由互斥锁来保护。很明显,您有可能收到多余的通知或丢失通知,但如果这是您的程序的可接受或可恢复的错误条件,我不相信标准中会有任何内容会导致其不合法。当然,这个标准不会防止你对抗竞争条件;程序员有责任确保竞赛条件良好。 (当然,程序员不应该放置任何“数据竞赛”,这些数据竞赛在标准中有非常明确的定义,但不直接适用于同步原语,或者未定义的行为被召唤。)

( 2)我无法回答这样的关于标准库设施的内部实现的问题。当然,供应商有责任提供正确工作并符合规格的图书馆设施。这个库的实现可能有一些内部状态,需要互斥以避免损坏,或者它可能会执行锁定以避免丢失或重复的通知。 (只是因为你的程序可以容忍它们,并不意味着图书馆的任意用户可以,而且一般情况下我希望它们不能)。我只是猜测他们在用这个互斥体保护什么。

(3)condition_variable_any适用于任何类似锁的物体,而condition_variable专门设计用于unique_lock<mutex>。后者可能比前者更容易实现和/或更高性能,因为它明确地知道它正在操作哪种类型以及它们需要什么(它们是否微不足道,它们是否适合高速缓存行,它们是否直接映射到特定平台的一系列系统调用,它们所暗含的保护范围或缓存一致性等),而前者提供了用于在锁定对象上操作的通用工具,而不会受到std::mutexstd::unique_lock<>的约束限制。

+0

也许我应该添加到Q:condition_variable_any的实现仅仅是condition_variable + mutex的薄包装。 – qble 2013-04-08 20:35:04

+0

“只是因为你的程序可以容忍他们,并不意味着图书馆的任意用户可以,而且一般情况下,我希望他们不能” - 我认为你不明白我的观点 - 如果我不保护我的notify_one通过我自己的互斥 - 可能会丢失通知,但如果我保留它 - 它们应该是不可能的。当用户不通过互斥保护notify_one时,实现不应该关心丢失的通知。 – qble 2013-04-08 20:38:32

+0

@qble:我再次无法评论标准库供应商选择如何实现标准的实现。他们可以使用任何编译器的魔法来使其运行(包括未定义的行为,如果他们碰巧知道他们的平台定义了它),并且他们可以利用非常具体的知识。这可能是因为他们知道std :: lock_guard 对他们来说足够原子化,但与模板参数关联的类型可能不会。 您可能会在执行wait()时发现一条线索。 – 2013-04-08 22:13:45