如果必须检查溢出或有条件地执行操作,std::atomic 是否是多余的?

问题描述 投票:0回答:1

例如,您可以安全地递增和递减 std::atomic_int 。但是,如果您需要检查溢出或根据该值有条件地执行某些例程,那么无论如何都需要锁。由于您必须比较该值,并且在比较成功后线程可能会被交换,另一个线程会修改,...bug。

但是如果你需要锁,那么你可以使用普通整数而不是原子。我说得对吗?

c++ c++11 overflow atomic stdatomic
1个回答
3
投票

不,你仍然可以有条件地使用 std::atomic 。

首先,

.fetch_add
的溢出行为被明确定义为原子类型的无符号或2的补码环绕(尽管可能不是您想要的)。与非原子类型不同,整数溢出仍然是 UB。

你是对的,

foo++
然后单独读取
foo
将是一个错误,所以使用
int tmp = foo++;
来使用原子RMW操作的返回值。

如果您绝对必须检查溢出,或以其他方式有条件地执行操作,则可以使用比较交换。这使您可以读取该值,决定是否要对其进行处理,然后自动更新该值(如果它没有更改)。这里的关键部分是系统会告诉你原子更新是否失败,在这种情况下你可以回到开始并读取新值并再次做出决定。

举个例子,如果我们只想将原子整数的最大值设置为 4(例如,在某种引用计数中),我们可以这样做:

#include <atomic>

static std::atomic<int> refcount = 0;

int val = refcount; // this doesn't need to be in the loop as on failure compare-exchange-strong updates it
while(true)
{
   if(val == 4)
   {
       // there's already 4 refs here, maybe come back later?
       break;
   }

   int toChangeTo = val + 1;
   if(refcount.compare_exchange_weak(val, toChangeTo))
   {
       // we successfully took a ref!
       break;
   }

    // if we fail here, another thread updated the value whilst we were running, just loop back and try again
}

这样的循环通常写为

do{...}while(!refcount.compare_exchange_weak(...));

我们使用

compare_exchange_weak
代替
compare_exchange_strong
。有时这可能会错误地失败,因此您需要循环执行此操作。然而,无论如何我们都有一个循环(一般来说,当你需要处理真正的失败时,你总是会这样做),所以
compare_exchange_weak
在这里很有意义。

在某些 ISA 上,需要一个 asm 循环来实现

compare_exchange_strong
,但不需要
weak
;在 x86 等其他 ISA 上,两者都编译为相同的 asm。只要准备下一次
weak
尝试的代码像本例中那样便宜,就不会使用
compare_exchange_weak
使任何事情变得更糟。

© www.soinside.com 2019 - 2024. All rights reserved.