【问题标题】:Does std::atomic provide atomic behavior, regardless of ordering?std::atomic 是否提供原子行为,无论排序如何?
【发布时间】:2017-08-31 02:39:58
【问题描述】:

如果使用std::atomic 模板声明变量,例如std::atomic<int>,是否保证通过std::atomic 中的方法进行访问将导致一致 值(即,一个通过std::atomic 方法写的)不管排序?

据我所知,这相当于询问 readswrites 是否可以“撕裂”——也就是说,在 ISA 可见的多个部分中写入或真实级别。

【问题讨论】:

  • the documentation for std::atomic。它回答了你所有的问题。如果您在理解该文档的某些部分时遇到问题,您需要准确解释您不确定的描述部分。学习如何阅读技术文档是每个 C++ 开发人员必备的技能。
  • 正如山姆所说! [[[
  • 这不是一个真正的答案,但不管std::atomic,指针大小或更小的对齐写入在当今所有主要的 ISA 上都是原子的(x86/x86_64、ARM、AArch64 )。对于多线程场景,这通常是不够的,但可以肯定的是,使用std::atomic 不会让事情变得更糟。
  • @SamVarshavchik - 我对该文档进行了一定长度的扫描,但没有看到任何特别提到原子性的内容。
  • 您是否在第一段中看到了“定义明确”的部分,其中包含指向 memory_order 规范的链接,详细地说,所有读取和写入会发生什么?跨度>

标签: c++ concurrency memory-model stdatomic


【解决方案1】:

原子,from the Greek atom meaning indivisible,是“不撕裂”的代名词。这意味着整个操作不可分割地发生。 你可以用 std::atomic 类型做的所有事情都是原子的(没有撕裂)。

C++14 draft N4140 部分29.3 顺序和一致性 是该章的第一部分,详细介绍。第一点之一是(1.4):

[ 注意:指定memory_order_relaxed 的原子操作在内存排序方面是宽松的。 实现仍必须保证对特定原子对象的任何给定原子访问是不可分割的 关于对该对象的所有其他原子访问。 ——尾注]

就阐述原子性要求的技术语言而言,每个操作(如.store().load().fetch_add())都使用如下语言定义:

§ 29.6.5 对原子类型的操作要求

void atomic_store(volatile A * object, C desired) noexcept;
void atomic_store(A * object, C desired) noexcept;
void atomic_store_explicit(volatile A * object, C desired, memory_order order) noexcept;
void atomic_store_explicit(A * object, C desired, memory_order order) noexcept;
void A ::store(C desired, memory_order order = memory_order_seq_cst) volatile noexcept;
void A ::store(C desired, memory_order order = memory_order_seq_cst) noexcept;
  1. 要求:订单参数不得为memory_order_consumememory_order_acquire,也不得为 memory_order_acq_rel.
  2. 效果:自动替换 object 或 this 指向的值为所需的值。 根据 order 的值影响内存。

等等,在所有适用的情况下都使用原子这个词。

对于add/+sub/-|&^,它们没有重复自己,而是有一个键/操作表适用于这个块:

C atomic_fetch_key (volatile A * object, M operand) noexcept;
C atomic_fetch_key (A * object, M operand) noexcept;
C atomic_fetch_key _explicit(volatile A * object, M operand, memory_order order) noexcept;
C atomic_fetch_key _explicit(A * object, M operand, memory_order order) noexcept;
C A ::fetch_key (M operand, memory_order order = memory_order_seq_cst) volatile noexcept;
C A ::fetch_key (M operand, memory_order order = memory_order_seq_cst) noexcept;
  • 28 效果:原子地替换对象所指向的值 this 将计算结果应用于指向的值 通过对象或 this 和给定的操作数。记忆受到影响 根据订单的价值。这些操作是原子的 读-修改-写操作 (1.10)。
  • 29 返回:原子地,值 在效果之前由对象或 this 指向
  • 30 备注:对于有符号整数类型,算术定义为使用二进制 补表示。没有未定义的结果。地址 类型,结果可能是未定义的地址,但操作 否则没有未定义的行为。

唯一可选的是与其他线程中的加载/存储进行排序/同步(对于没有同步的原子性,请使用memory_order_relaxed)。

事实上,没有办法“关闭”原子性来加载昂贵的宽类型(例如在 x86 上的 a CAS on atomic<pointer_and_ABAcounter> which compiles to lock cmpxchg16b 之前)。我在那个答案中使用了联合黑客来有效地加载结构。

更重要的是,联合是 gcc 的一种解决方法,它不优化 ptr_and_counter.ptr 以仅加载指针,我认为至少在 x86 上是安全的。相反,gcc 坚持以原子方式加载整个结构并 然后 从结果中获取指针。当它是 x86-64 上的 16 字节结构时,这非常糟糕,而在 x86-32 上则相当糟糕。 (见https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80835

【讨论】:

  • 感谢标准报价。有趣的是,他们在这里引入了一个新术语,indivisible 来描述值必须是原子的,因为我想说“原子值必须仍然是原子的”会很奇怪。有趣的是,他们在relaxed 的上下文中提到了这一点,就像“它们仍然必须是不可分割的”一样——但他们是否在其他地方提到了整体不可分割性?
  • @BeeOnRope:是的。宽松部分中的措辞暗示其他排序也具有该属性。如果原子总是原子的,你想知道什么?我认为很明显,除了编译器错误,它们总是原子的。
  • 当我问这个问题时,我想我是困了或喝醉了,但后来我并没有删除,我仍然想看一下拼写出来的文字。我没有标准的副本,但如果它有像​​ cppreference 那样的“将读取由其他线程写入的值”之类的文本,那么我想就是这样。
  • @BeeOnRope:如果您单击我添加的链接,您可以获得标准草案的 pdf 副本...如果您找到您正在寻找的语言,请随时编辑此答案。我同意最好用标准中的最佳报价来回答这个问题,但没有时间去挖掘它。
  • @BeeOnRope:使用标准中的技术语言进行了更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-05-01
  • 2016-06-05
  • 2015-11-29
  • 1970-01-01
  • 2013-05-28
  • 2023-03-23
  • 1970-01-01
相关资源
最近更新 更多