You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++无锁并发示例安全性疑问:为何会出现数据覆盖?

为什么使用acquire/release语义仍会出现共享数组被覆盖的问题?

嘿,我完全理解你的困惑——你已经用上了原子变量的acquire/release内存语义,也用了原子索引来管理共享数组,结果却还是出现了线程覆盖数据的情况,对吧?咱们把这个问题拆解清楚:

核心误区:acquire/release不保证操作序列的原子性

首先要明确:acquire/release语义解决的是内存可见性和操作顺序一致性问题,但它无法保证多个独立原子操作组成的序列是原子的。

你提到的场景里,大概率是类似这样的执行流程(模拟常见的错误实现):

// 共享变量
std::atomic<bool> flag_atomic{true};
std::atomic<int> idx_atomic{0};
int shared_array[100];

// 线程1/线程2的执行逻辑
void thread_func(int value) {
    if (flag_atomic.load(std::memory_order_acquire)) {
        int idx = idx_atomic.load(std::memory_order_relaxed);
        shared_array[idx] = value;
        idx_atomic.store(idx + 1, std::memory_order_release);
    }
}

问题出在操作的间隙

我们来看一个典型的并发时序:

  1. 线程A调用flag_atomic.load(acquire),得到true,确认可以写入
  2. 线程B几乎同时调用flag_atomic.load(acquire),同样得到true
  3. 线程A加载idx_atomic的值,得到0
  4. 线程B也加载idx_atomic的值,同样得到0(因为线程A还没执行后续的store操作)
  5. 线程A把自己的value写入shared_array[0]
  6. 线程A把idx_atomic更新为1(release语义确保这个更新对后续acquire的线程可见)
  7. 线程B把自己的value写入shared_array[0],直接覆盖了线程A的数据
  8. 线程B把idx_atomic更新为1

这里的关键是:检查flag和获取索引是两个独立的原子操作,中间没有任何互斥或同步机制阻止其他线程插入执行。acquire/release只能保证当线程A更新idx_atomic后,后续线程加载idx_atomic能看到最新值,但它管不了线程B在线程A更新索引前就已经拿到了旧的索引值。

怎么解决这个问题?

要避免覆盖,你需要把“获取可用索引”变成一个原子的递增操作,而不是分开的load和store。比如用fetch_add原子地获取下一个索引:

void thread_func(int value) {
    if (flag_atomic.load(std::memory_order_acquire)) {
        int idx = idx_atomic.fetch_add(1, std::memory_order_acq_rel);
        if (idx < 100) { // 确保数组不越界
            shared_array[idx] = value;
        }
    }
}

fetch_add是一个单一的原子操作,它会同时完成“读取当前值”和“递增并写入新值”的动作,这样两个线程绝对不会拿到同一个索引。

另外,如果你的flag_atomic是用来控制是否允许写入的开关,还要注意:如果flag被设置为false的时机和索引操作没有同步,还是可能出现问题,但核心的覆盖问题已经通过原子递增解决了。

再总结一下

acquire/release语义的作用是确保:

  • 一个线程在release操作前的所有内存写操作,对后续acquire该原子变量的线程可见
  • 操作的执行顺序不会被编译器或CPU乱序

但它不能阻止多个线程同时进入写入逻辑——只要你的操作序列不是原子的,并发线程就可能在间隙中插入执行,导致数据覆盖。

内容的提问来源于stack exchange,提问作者Joe Oram

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:33:05