std::atomic自交换是否为原子操作?两段模增代码原子性疑问
std::atomic相关原子性问题解答
1. 将std::atomic对象与自身进行exchange操作的原子性
std::atomic::exchange本身是原子操作,它会原子地把原子对象的值替换为传入的参数,并返回旧值。但要注意:如果exchange的参数依赖该原子对象的当前值,参数的计算过程是在exchange原子操作之外的,整个「计算参数+执行exchange」的组合逻辑并不是原子的。
2. 第一段代码是否原子执行?
代码:
const int oldId = id.exchange((id.load()+1) % maxId);
这段代码不是原子执行,原因如下:
id.load()是原子操作,但它返回的只是当前线程读取到的id的快照值。- 后续的
+1和% maxId是普通非原子计算,在这个过程中,其他线程完全可以修改id的值。 - 最后
exchange虽是原子操作,但它使用的是之前基于旧快照计算出的结果,会引发竞态条件:比如线程A读取id为2,线程B同时读取id为2并修改为3,线程A最终会把id设置为3((2+1)%maxId),直接覆盖线程B的修改,导致数据不一致。
3. 第二段代码是否具备原子性?
代码:
const int newId = id.exchange((++id) % maxId);
这段代码既不具备原子性,逻辑还存在错误:
++id是std::atomic的原子自增操作,会原子地将id加1并返回新值,但执行完成后,% maxId是非原子计算,期间其他线程仍可修改id。- 更关键的是:
++id已经修改了id的值,之后的exchange又会把id替换为(++id的结果)%maxId,相当于对id进行了两次修改,这两次操作之间存在间隙,完全可能被其他线程打断,同时这也不是你想要的「模增」逻辑(比如id原本是maxId-1,++id后变成maxId,exchange再改成0,但中间若有其他线程修改id,结果会彻底混乱)。
正确的原子模增实现方式
要实现原子的「模增」操作,推荐使用std::atomic::compare_exchange_weak(或compare_exchange_strong)的循环模式,它能保证「读取-计算-写入」的逻辑在无竞争时原子完成,有竞争时自动重试:
int current = id.load(); int desired; do { desired = (current + 1) % maxId; // 尝试将id从current替换为desired,成功则退出循环,失败则更新current为当前id值并重试 } while (!id.compare_exchange_weak(current, desired)); // 此时current是替换前的旧值,desired是最终写入的新值
如果maxId是2的幂,也可以用fetch_add配合位运算实现,但这种方式仅适用于特定场景,通用场景还是优先选择compare_exchange的循环实现。
内容的提问来源于stack exchange,提问作者pajczur
相关产品推荐
相关产品推荐

