【问题标题】:Should a boolean flag always be atomic?布尔标志是否应该始终是原子的?
【发布时间】:2016-01-20 11:00:54
【问题描述】:

假设有一个由主线程控制的布尔标志 (keep_running)。另一个线程无限循环,直到该标志变为假。

int main() {
    bool keep_running(true);
    std::thread run( [](bool &keep_running)
    {
        while(keep_running)
        {
            // do work
        }
    }, std::ref(keep_running) );

    // do other work
    keep_running = false;
    run.join();
    return 0;
}

该标志应该是原子的吗?

std::atomic<bool> keep_running

我想,非原子版本可能发生的最糟糕的情况是标志在发生时设置正确

while(keep_running)

被执行。在这种情况下,循环会继续运行一次(并非严格需要)迭代。但就我而言,这是可以接受的。

是否存在上述代码可能出错的情况?

编辑:

出于性能原因(并且没有错误),我对此最感兴趣。因此,在循环中使用 std::atomic 作为标志会对性能产生负面影响吗?

【问题讨论】:

  • 在旧版本的 C++ 中,您会使用volatile - 没有它,从一个线程对变量的写入可能不会从另一个线程看到。但是,由于在 C++11 中有 atomic,您应该使用它来防止同样的问题。
  • 当然应该是原子的。这为您提供了正确的语义。你为什么想做其他事情?
  • 我在想,我既不需要围绕标志的内存屏障,也不需要关心指令重新排序。因此,我认为我可以摆脱 std::atomic 以获得性能。我试图通过编辑问题来澄清这一点。但我没有意识到变量可能会卡在寄存器中,正如 Tsyvarev 在下面的回答中指出的那样。

标签: c++ multithreading c++11 atomic


【解决方案1】:

只是 C++11 标准禁止(将它们标记为 Undefined Behaviour)并发访问非原子变量。

所以你需要声明这个变量是原子的。

请注意,使用keep_running.load(std::memory_order_relaxed) 读取值和keep_running.store(true, std::memory_order_relaxed) 写入值将消除任何额外的性能成本,因此生成的代码将与没有原子的代码一样快。


我想,非原子版本可能发生的最糟糕的情况是标志在当时被正确设置。

在您的线程中,变量可能会被存储到寄存器中并且永远不会重新加载(因此线程永远不会停止)。如果没有atomic 或其他特殊类型和修饰符,编译器就可以这样做,而且确实如此。

【讨论】:

  • 我会赞成,但认为“可能发生的最坏情况是变量将存储到线程中的寄存器中并且永远不会重新加载”的说法具有误导性。可能发生的最坏情况是关于 UB 的任何事情。编译器可以做任何事情来响应它,包括程序中任何一点的失败。请对此添加注释,这将是一个很好的答案。
  • 这篇文章的开头是关于 UB 的概念,并且它被标准禁止。我认为没有必要在最后的信息部分重复这一点。但我已经删除了“最糟糕”的词,所以这只是可能的糟糕场景之一,而不是唯一的。
  • 谢谢,那是完美的,我并不是说应该重复,只是让 OP 认为这是可能发生的唯一不良影响,“最坏的”似乎到 +1
  • 不同意'将消除任何额外的性能成本'。在非缓存一致的系统上不是这样。
  • @Tsyvarev,很简单——在非连贯系统上,原子写入,即使是放松的,也必须传播到所有 CPU,而常规写入除了本地缓存之外不必去任何地方。当然,在缓存一致的系统上也是如此。实际上,如果不是针对非缓存一致性系统,实际上没有理由一开始就放松内存模型 - 简单的 volatile 就可以了。
猜你喜欢
  • 2017-10-12
  • 2021-05-16
  • 1970-01-01
  • 2016-02-16
  • 2021-02-21
  • 2017-02-11
  • 2015-01-30
  • 1970-01-01
  • 2021-10-11
相关资源
最近更新 更多