我需要 std::atomic<bool> 还是 POD bool 足够好?
Posted
技术标签:
【中文标题】我需要 std::atomic<bool> 还是 POD bool 足够好?【英文标题】:Do I need std::atomic<bool> or is POD bool good enough? 【发布时间】:2017-11-24 21:46:03 【问题描述】:考虑这段代码:
// global
std::atomic<bool> run = true;
// thread 1
while (run) /* do stuff */
// thread 2
/* do stuff until it's time to shut down */
run = false;
我在这里需要与原子变量相关的开销吗?我的直觉是,布尔变量的读/写或多或少是原子的(这是常见的 g++/Linux/Intel 设置),如果有一些写/读时序怪异,我在线程 1 上的运行循环会停止一个结果早晚通过,我对这个应用程序并不担心。
或者还有其他一些我在这里遗漏的考虑因素吗?查看性能,看来我的代码在std::atomic_bool::operator bool
中花费了相当多的时间,我宁愿将它放在循环中。
【问题讨论】:
"看来我的代码在std::atomic_bool::operator bool
中花费了相当多的时间" 您是否在编译时进行了优化?那东西应该完全内联。
【参考方案1】:
您需要使用std::atomic
来避免不希望的优化(编译器读取一次值并且总是循环或从不循环)并在没有强排序内存模型的系统上获得正确的行为(x86 是强排序的,所以一旦写入完成,下一次读取会看到它;在其他系统上,如果线程由于其他原因没有将 CPU 缓存刷新到主 RAM,则写入可能很长时间都看不到,如果有的话)。
不过,您可以提高性能。默认使用std::atomic
使用a sequential consistency model,这对于单个标志值来说是多余的。您可以通过使用带有显式(且不那么严格)的内存排序的load
/store
来加速它,因此每个load
都不需要使用最偏执的模式来保持一致性。
例如,你可以这样做:
// global
std::atomic<bool> run = true;
// thread 1
while (run.load(std::memory_order_acquire)) /* do stuff */
// thread 2
/* do stuff until it's time to shut down */
run.store(false, std::memory_order_release);
在 x86 机器上,任何比(默认、最严格)顺序一致性排序不那么严格的排序通常最终只会确保指令以特定顺序执行;由于强排序的内存模型,不需要总线锁定等。因此,除了保证值实际上是从内存中读取的,而不是缓存到寄存器并重用之外,在 x86 上以这种方式使用原子是免费的,并且在非 x86 机器上,它使您的代码正确(否则它不会是)。
【讨论】:
感谢您的详细解答!非常感谢。 @ShadowRanger 关于您的声明(x86 是强排序的,所以一旦写入完成,下一次读取就会看到它).. 这并不完全准确,因为X86
确实如此默认情况下不阻止#StoreLoad 重新排序。如果您需要该保证,则需要完整的围栏(或xchg
)。
在典型的 x86 实现中,SC 负载和非 SC 负载之间没有性能差异。唯一的区别是在 store 中,在这段代码中只发生一次。
+1 表示正确答案,但可能值得一提的是,顺序一致性更容易推理,所以最好的建议是这样做,直到客户对性能和分析确认它是内存栅栏故障(不会)。
@RichardHodges:的确,顺序一致性很难出错。我怀疑在这种情况下,即使是轻松的排序也会起作用,但我的偏执使我使用release
/acquire
语义,以防在关闭过程中确实存在必须从父线程可见的数据。我同意这不太可能是性能问题;除非循环几乎是空的,否则在任何实际工作的上下文中,完整的内存栅栏的成本可能很小。以上是关于我需要 std::atomic<bool> 还是 POD bool 足够好?的主要内容,如果未能解决你的问题,请参考以下文章
ARM 上的 std::atomic<bool> 无锁不一致(树莓派 3)
标准的 atomic bool 和 atomic flag 之间的区别
cpp multi thread sync via std::atomic<bool;