为什么 std::fetch_add returns 旧值?

Why std::fetch_add returns the old value?

是什么设计目的或技术限制使std::fetch_add的return值变成了更改前的值?

这两种方式都没什么大不了的,你可以模仿另一种方式。例如如果需要,val.add_fetch(1) 可以用 1 + val.add_fetch(1) 实现。然而,GNU C __atomic builtins provide both.

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

大多数具有 LL/SC 原子的 RISC ISA 有 3 个操作数指令,所以它们可以 add dst, src1, src2 并且如果代码稍后需要它,它们可以将内存中的值原封不动地留在另一个寄存器中。 (LL/SC fetch_add(x) 通常会实现为 load-linked reg1, [mem] / add reg2, reg1, x / store-conditional reg3, reg2, [mem]。使用基于 reg3 中 success/fail 结果的重试循环。如果 fetch_add return 值未使用,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);

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