当类型不是 Integral 时,如何使用 std::atomic 执行基本操作?

Posted

技术标签:

【中文标题】当类型不是 Integral 时,如何使用 std::atomic 执行基本操作?【英文标题】:How to perform basic operations with std::atomic when the type is not Integral? 【发布时间】:2014-05-31 17:30:31 【问题描述】:

确切地说,我只需要将一个 double 增加另一个 double 并希望它是线程安全的。我不想为此使用互斥锁,因为执行速度会大大降低。

【问题讨论】:

相关:Atomic double floating point or SSE/AVX vector load/store on x86_64。基本上与此相同的答案,但具有 x86 asm 详细信息。 (不幸的是,有些编译器在将数据从 XMM 转换为 compare_exchange 的整数甚至使用 atomic<double> 加载/存储时效率相当低。) 【参考方案1】:

所以使用积分原子作为内存屏障。这是一个带有来源和解释的页面:http://preshing.com/20121019/this-is-why-they-call-it-a-weakly-ordered-cpu/

【讨论】:

内存屏障只有助于排序,而不是原子性。这就是为什么您需要一个 CAS(或 LL/SC)来使整个读-修改-写原子化。【参考方案2】:

通常,C++ 标准库试图只提供可以有效实现的操作。对于std::atomic,这意味着可以在“通用”架构上的一条或两条指令中无锁执行的操作。 “通用”架构具有用于整数的原子获取和添加指令,但不适用于浮点类型。

如果您想为原子浮点类型实现数学运算,您必须自己使用 CAS(比较和交换)循环 (Live at Coliru):

std::atomic<double> foo0;

void add_to_foo(double bar) 
  auto current = foo.load();
  while (!foo.compare_exchange_weak(current, current + bar))
    ;

【讨论】:

谢谢你的回答,我没有意识到为了做到这一点(安全地添加两个双线程)我需要这么脏(或者至少在我看来是这样)的解决方法。 我有点困惑。如果 foo != current 由于 foo 被负载和 CAS 之间的另一个线程修改,为什么这段代码不会进入无限循环? @Joe compare_exchange_weak 通过引用获取其第一个参数,并在失败时将其更新为观察值。因此,如果 CAS 因foo != current 而失败,则循环会从更新的current 中计算出一个新的current + bar 并重试。 @Casey 感谢您的澄清。

以上是关于当类型不是 Integral 时,如何使用 std::atomic 执行基本操作?的主要内容,如果未能解决你的问题,请参考以下文章

如何知道某些数据类型是不是来自 std?

如何在Integral类型中获得sqrt的楼层

使用类作为数据类型时如何在 std::variant 中存储值?

具有特征的 C++ 类型擦除

测试 std::pointer_traits 是不是可以与我的类型一起使用

使用 std::map 时,我应该为键类型重载 operator== 吗?