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

Posted

技术标签:

【中文标题】我是不是需要同步 std::condition_variable/condition_variable_any::notify_one【英文标题】:Do I need to synchronize std::condition_variable/condition_variable_any::notify_one我是否需要同步 std::condition_variable/condition_variable_any::notify_one 【发布时间】:2013-04-08 19:30:01 【问题描述】:

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

所以,问题:

    对于condition_variable_anycondition_variable,不通过互斥锁保护notify_one 是否是线程安全的? 为什么 condition_variable_any 的实现使用额外的互斥锁? 为什么condition_variable_any::notify_onecondition_variable::notify_one 的实现不同?也许condition_variable::notify_one 需要手动保护但condition_variable_any::notify_one 不需要?是 libstdc++ 错误吗?

【问题讨论】:

【参考方案1】:

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

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

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

是的。

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

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

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

condition_variable_any 可以与任何Lockable 类型一起使用,不仅仅是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,则必须锁定内部互斥锁以避免错过通知。

【讨论】:

谢谢!这对我帮助很大。特别是回答 2 和 3 - 关于原子性的好点,以及无论如何使用互斥锁的内部东西。顺便说一句,您可以添加指向 pthread_cond_wait pubs.opengroup.org/onlinepubs/7908799/xsh/… 的链接,以表明最常见的实现仅适用于它自己的内部互斥锁。您能否澄清一下“那里有一些非常微妙的代码”到底是什么意思。 关于 1. - 非锁定 condition_variable::notify_one 会导致错过通知吗? IE。线程 #1 在互斥锁下执行工作,发送结果,解锁互斥锁,[同时] 线程 #2 锁定互斥锁但尚未调用等待,[同时] 线程 #1 调用 notify_one,[同时] 线程 #2 调用等待 - 通知丢失。 这不是丢失通知,这是等待线程在等待之前没有检查条件谓词。锁定互斥锁对这种情况没有帮助,通知仍然可能在等待线程锁定互斥锁之前出现。您必须在等待条件变量时检查关联的谓词 即使有谓词检查泄漏也是可能的:while(pred()) /*meanwhile notify_one can happen here*/ cond.wait(m); - 通知丢失。 (带 1-arg wait @qble,仅当您的程序损坏时。如果检查时谓词为假,如何在解锁互斥锁之前变为真?【参考方案2】:

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

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

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

【讨论】:

也许我应该添加到 Q:condition_variable_any 的实现只是 condition_variable+mutex 的薄包装。 “仅仅因为你的程序可以容忍它们,并不意味着库的任意用户可以,而且总的来说我希望他们不能” - 我认为你不明白我的意思 - 如果我自己不会通过互斥锁保护我的 notify_one - 可能会丢失通知,但如果我保护它 - 它们应该是不可能的。当用户不通过互斥锁保护 notify_one 时,实现不应该关心丢失的通知。 @qble:同样,我不能真正评论标准库供应商选择做什么来实现他们的标准实现。他们被允许使用任何编译器魔法来让它工作(包括未定义的行为,如果他们碰巧知道他们的平台定义了它)并且他们可以利用非常具体的知识。他们可能知道 std::lock_guard<:mutex> 对他们来说足够原子化,但与模板参数关联的类型可能不知道。您可能会在他们的 wait() 实现中找到线索。

以上是关于我是不是需要同步 std::condition_variable/condition_variable_any::notify_one的主要内容,如果未能解决你的问题,请参考以下文章

在使用 Ensembles 进行 CoreData 和 iCloud 同步之前,我是不是需要任何 iCloud 设置?

同步核心数据时,是不是需要调用 URLForUbiquityContainerIdentifier: ?

操作不同的数组(对象数组)索引时是不是需要同步

SyBase SQL 随处检查是不是需要同步?

这些 Boost::Interprocess 组件是不是需要同步?

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