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

C++原子操作内存序问题咨询:修改顺序与RMW操作行为

C++原子操作内存顺序问题分析

原代码场景

代码示例

#include <memory>
#include <atomic>
#include <iostream>
#include <thread>
#include <cassert>

int main()
{
    std::atomic<bool> flag = {false};
    std::atomic<int> val = {0};

    std::thread t1([&](){
        val.store(1, std::memory_order::relaxed);  // #1
        flag.store(true, std::memory_order::relaxed);  // #2
    });

    std::thread t2([&](){
        while(!flag.load(std::memory_order::relaxed)){}  // #3
        auto r = val.exchange(2, std::memory_order::relaxed); // #4
        assert(r != 0);  // #5
    });

    t1.join();
    t2.join();
}

问题解答

问:示例中#3与#2无同步关系,但可保证#4执行时#1已完成。这是否意味着#4执行时val的修改顺序为{0,1},依据[atomics.order] p10规定(原子读-改-写操作必须读取该操作写入前修改顺序的最后值),#4必定读到1?#5的断言是否一定不会触发?

答:

  • 首先,t2的#4必须在#3读到true后才会执行,而#3读到true意味着t1的#2已经完成。由于同一线程内操作有happens-before关系,#1在t1中先于#2执行,因此#1必然在#4之前完成。
  • 对于val的全局修改顺序,此时只有初始值0和#1写入的1两个修改(#4的写入还未发生)。根据标准[atomics.order] p10,原子读-改-写操作必须读取该对象修改顺序中自身写入前的最后一个值,因此#4必定读到1。
  • 综上,#5的断言r != 0一定不会触发,因为r的值只能是1。

更新后代码场景

代码示例

#include <memory>
#include <atomic>
#include <iostream>
#include <thread>
#include <cassert>

int main()
{
    std::atomic<bool> flag = {false};
    std::atomic<int> val = {0};

    std::thread t1([&](){
        val.store(1, std::memory_order::relaxed);  // #1
        val.exchange(2, std::memory_order::relaxed); // #2
        flag.store(true, std::memory_order::relaxed);  // #3
    });

    std::thread t2([&](){
        while(!flag.load(std::memory_order::relaxed)){}  // #4
        // auto r = val.load(std::memory_order::relaxed);
        auto r = val.exchange(3, std::memory_order::relaxed); // #5
    });

    t1.join();
    t2.join();
}

Q1解答

问:此示例中#2可能返回哪些值?直观情况是返回#1存储的1,能否读到#5存储的3?

答:

  • #2只能返回1,不可能读到#5写入的3。原因是:同一线程内#1 happens-before #2,#2 happens-before #3;而t2的#5必须在#4读到true(即#3完成)后才会执行,因此#2的执行必然早于#5,#5的写入3不可能被#2读取到。

Q2解答

问:若#2读到1,#5是否必须读取#2存储的2,还是也能读到0?若#5改为注释中的纯load操作,结果可能为0、1或2吗?

答:

  • 当#2读到1时,#5作为原子读-改-写操作,必须读取val修改顺序中自身写入前的最后值。由于#2的写入2在#5执行前已经完成(#2 happens-before #3,#5在#3之后执行),因此#5必定读到2,不可能读到0。
  • 如果#5改为val.load(std::memory_order::relaxed):由于flag的操作是relaxed内存顺序,#3和#4之间没有synchronizes-with关系,导致#2和#5之间也没有happens-before关系。此时#5的relaxed load可能读取到val的初始值0、#1写入的1,或者#2写入的2——这符合C++标准对relaxed内存顺序的规定:只要读取值是该原子对象全局修改顺序中早于读取操作的某个值即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 04:27:34