【问题标题】:c++ double check if statement optimization with a lockc ++仔细检查是否带有锁的语句优化
【发布时间】:2015-10-31 20:38:38
【问题描述】:


我的问题不是关于双重检查锁定,而是类似的。
在下面的代码中,编译器是否可以优化出内部的if语句?

无效 MyClass:foo() { if(x) //MyClass的成员数据定义为bool { loc.lock(); //互斥体。超出范围时解锁 if(x) //编译器可以优化这个吗? { fire1();//doSomthingNotAffectingX } } }

X 通过同一翻译单元中的另一个线程设置。

无效 MyClass::unset() { loc.lock(); x=假; 火2(); }

这个想法是保证如果调用了fire2,就不能调用fire1

【问题讨论】:

  • 无法保证 bool 是原子的,您需要使用 C++11 中的 std::atomic 或旧平台特定类型才能接近您想要的。
  • 如果我在互斥锁中,为什么需要原子?
  • 你的第一个if(x)在锁外
  • @Flexo,如果 x 从未设置为 true(创建时除外),那么第一个 if 在外面并不重要。第一个if 只是为了避免锁定而进行的优化。类似的模式通常用于单例。
  • @Flexo 如果变量受mutex 保护,您也不需要将其设为atomic。阅读std::mutex 文档。

标签: c++ optimization compiler-construction


【解决方案1】:

在我的回答中,我假设您使用的是 std::mutex 而不是您自己的自定义类!

本质上,编译器无法优化第二个if

好吧,如果编译器能够以某种方式确定x 不能(合法地)更改,它可能会删除检查,但显然,这里不是这种情况。只有当生成的程序运行AS-IF时,编译器才能进行优化。

在 C++ 11 及更高版本中,互斥体是障碍。因此,如果变量受到互斥锁的适当保护,您将读取预期值。但是,如果您忘记在适当的位置放置一些互斥锁,那么行为将是未定义的。

在这里,由于您的第一次检查是读取,您可能会遇到问题,如果 x 在被设置为 false 后可以再次变为 true

Memory model

C++11 introduced a standardized memory model. What does it mean? And how is it going to affect C++ programming?

我强烈推荐阅读 Anthony Williams 的书 C++ Concurrency in Action,实用多线程。这将有助于理解现代 C++ 多线程。

在您的代码中,您是否需要通过互斥锁保护 fire1fire2 如果该函数还等待 mutex,则调用该函数可能会很危险,因为它可能导致僵局。此外,lock 的有效时间可能比要求的要长。如果您只需要确保在调用fire2 时不调用fire1,那么std::atomic<bool> 就足够了……

-- 编辑--

最后,文档中有一个很好的例子:std::mutex

-- 编辑#2--

正如评论中所指出的,我的回答对于一般情况并不完全有效。我确实认为上面的代码可以与bool 一起正常工作,前提是它只能从true 更改为false

于是我在网上搜索了一下,找到了那些文章:

【讨论】:

    【解决方案2】:

    如果编译器不知道除loc.lock() 之外的其他人可能会更改x,并且它知道loc.lock() 肯定不会更改x,那么它可以假设x 不会在两者之间更改ifs。在这种情况下,可能会将x 存储在寄存器中,或者完全省略if

    如果你想避免这种不安全的优化,你必须让编译器知道。在 C++11 上,使用 std::atomic<bool>。在早期版本中,将其设为volatile bool。两者都不做,编译器可能会破坏你的代码。

    【讨论】:

    • 锁不是内存屏障的高级形式吗?如果你说的是对的,那是不是意味着所有变量在多线程编程中都应该是 volatile 的?考虑一下这个
      if(flag) {if(foo()) where foo(
    • 超时。对不起。再试一次。锁不是内存屏障的更高形式吗?那么使用互斥锁有什么意义呢?如果您说的是对的,这是否意味着在多线程编程中所有变量都应该是 volatile 的,因为如果像上面的那样,某些内联函数最终可能会扩展为嵌套。但对程序员来说不是太明显。
    • 编译器如何知道锁是屏障?如果您使用专用语言结构(C++11),它可以;如果您使用专用的编译器扩展,它可以;否则,如果编译器不确定,它可能会保守并避免优化。但是如果你的锁是一个简单的自旋锁,编译器就没有理由认为它是一个内存屏障——只是一个简单的循环(也可能被优化)。至于volatile,这是一个穷人的解决方案,但如果这就是你所拥有的,那么所有共享的变量都必须是volatile(不能解决所有问题-查找易失性)。
    • @eran 如果您使用专用语言结构,那么编译器会以某种方式知道......而且编译器可能也知道操作系统提供的内容。因此,在实践中,唯一需要非常小心的情况就是编写自己的锁。
    • @eran 阅读文档std::mutex
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-26
    • 2014-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多