为什么 std::fetch_add 返回旧值?

Xin*_*gx1 1 c++ language-design atomic c++11 stdatomic

是什么设计目的或者技术限制导致返回值std::fetch_add变成了之前的值?

Pet*_*des 5

无论哪种方式都没什么大不了的,您可以根据其中一种来模仿另一种。例如,如果您愿意,val.add_fetch(1)可以使用它来实现。1 + val.fetch_add(1)然而,GNU C__atomic内置函数同时提供了.

fetch_addISO C/C++ 仅提供而不是提供的可能原因add_fetch:在某些情况下,它使得在 x86 上实现更便宜;lock xadd [mem], regleaves reg = mem 的旧值,mem = sum。提供该原语而不是其他原语会鼓励人们围绕该构建块设计算法,也许可以避免需要额外的add指令。

大多数具有 LL/SC 原子的 RISC ISA 都具有 3 操作数指令,因此add dst, src1, src2如果代码稍后需要,它们可以将内存中的值不受干扰地保留在另一个寄存器中。(LL/SCfetch_add(x)通常会实现为load-linked reg1, [mem]/ add reg2, reg1, x/ store-conditional reg3, reg2, [mem]。根据 reg3 中的成功/失败结果进行重试循环。如果fetch_add未使用返回值,则add可以覆盖reg1而不是使用新的 reg。

因此,在大多数 RISC 上,无论哪种方式都很好,而且 x86 是最关心效率的 ISA 之一。


对于某些用例,fetch_add这也是您想要的。例如,对于在基于数组的循环缓冲区无锁队列中抓取存储桶的线程,从std::atomic<unsigned> write_idx;零初始化开始,您希望.fetch_add0.

  static std::atomic<unsigned> write_idx = 0;  // shared var

  // in each thread:
  unsigned my_buf = write_idx.fetch_add(1) & ((1<<size) - 1);
Run Code Online (Sandbox Code Playgroud)

您将获得0以而不是开头的值1。对于许多用例来说,这似乎是一个合理的模式。