Is it atomic operation when exchange std::atomic with itself?

huangapple go评论68阅读模式
英文:

Is it atomic operation when exchange std::atomic with itself?

问题

以下是您要翻译的内容:

"Will following code be executed atomically?

const int oldId = id.exchange((id.load()+1) % maxId);

Where id is std::atomic<int>, and maxId is some integer value.

I searched google and stackoverflow for std::atomic modulo increment. And I found some topics but I can't find clear answer how to do that properly.

In my case even better would be to use:

const int newId = id.exchange((++id) % maxId);

But I am still not sure if it will be executed atomically."

英文:

Will following code be executed atomically?

const int oldId = id.exchange((id.load()+1) % maxId);

Where id is std::atomic&lt;int&gt;, and maxId is some integer value.

I searched google and stackoverflow for std::atomic modulo increment. And I found some topics but I can't find clear answer how to do that properly.

In my case even better would be to use:

const int newId = id.exchange((++id) % maxId);

But I am still not sure if it will be executed atomically.

答案1

得分: 3

这不是原子操作,因为load()exchange()是分开的操作,没有阻止idload之后但在exchange之前被更新。在这种情况下,您的exchange将写入基于过时输入计算的值,因此会出现更新遗漏的情况。

您可以使用简单的compare_exchange循环来实现模数增量:

int val = id.load();
int newVal = (val + 1) % maxId;
while (!id.compare_exchange_weak(val, newVal)) {
  newVal = (val + 1) % maxId;
}

如果compare_exchange失败,它会执行重新加载并将val填充为更新后的值。因此,我们可以重新计算newVal并重试。

编辑:

compare-exchange循环的整个目的是处理在loadcompare-exchange之间可能有人更改id的情况。思路是:

  1. 加载id的当前值
  2. 计算新值
  3. 只有当id中当前存储的值与我们在步骤1中读取的值相同时,才使用我们自己的值更新id。如果是这种情况,我们完成了,否则我们重新开始步骤1。

compare_exchange允许我们在一个原子操作中执行比较和条件更新。compare_exchange的第一个参数是期望的值(我们在比较中使用的值)。这个值是通过引用传递的。因此,当比较失败时,compare_exchange会自动重新加载当前值并更新提供的变量(在我们的情况下是val)。

由于Peter Cordes正确指出可以使用do-while循环来避免代码重复,这里是它的实现:

int val = id.load();
int newVal;
do {
  newVal = (val + 1) % maxId;
} while (!id.compare_exchange_weak(val, newVal));
英文:

No, this is not atomic, because the load() and the exchange() are separate operations, and nothing is preventing id from getting updated after the load, but before the exchange. In that case your exchange would write a value that has been calculated based on a stale input, so you end up with a missed update.

You can implement a modulo increment using a simple compare_exchange loop:

int val = id.load();
int newVal = (val + 1) % maxId;
while (!id.compare_exchange_weak(val, newVal) {
  newVal = (val + 1) % maxId;
}

If the compare_exchange fails it performs a reload and populates val with the updated value. So we can re-calculate newVal and try again.

Edit:

The whole point of the compare-exchange-loop is to handle the case that between the load and the compare-exchange somebody might change id. The idea is to:

  1. load the current value of id
  2. calculate the new value
  3. update id with our own value if and only if the value currently stored in id is the same one as we read in 1. If this is the case we are done, otherwise we restart at 1.

compare_exchange is allows us to perform the comparison and the conditional update in one atomic operation. The first argument to compare_exchange is the expected value (the one we use in our comparison). This value is passed by reference. So when the comparison fails, compare_exchange automatically reloads the current value and updates the provided variable (in our case val).

And since Peter Cordes pointed out correctly that this can be done in a do-while loop to avoid the code duplication, here it is:

int val = id.load();
int newVal;
do {
  newVal = (val + 1) % maxId;
} while (!id.compare_exchange_weak(val, newVal);

huangapple
  • 本文由 发表于 2023年2月14日 20:13:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/75447688.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定