C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++内存序

C++内存序的操作方法

作者:2301_80355452

在C++中,内存序(Memory Order)是一个非常重要的概念,特别是在多线程编程中,本文通过实例代码介绍C++内存序的相关知识,感兴趣的朋友一起看看吧

1.为什么需要内存序?

问题的根源:现代CPU的乱序执行

// 从程序员角度看是顺序执行
int x = 0, y = 0;
void thread1() {
    x = 1;  // 步骤1
    y = 1;  // 步骤2
}
void thread2() {
    if (y == 1) {
        assert(x == 1);  // 可能失败!
    }
}

实际执行可能:

2.六种内存序详解

内存序概览表

内存序作用使用场景性能
relaxed只保证原子性计数器、统计最快
consume数据依赖排序指针发布较快
acquire加载屏障读侧同步中等
release存储屏障写侧同步中等
acq_rel加载+存储屏障CAS操作较慢
seq_cst全序屏障默认,最安全最慢

3.逐层深入理解

3.1memory_order_relaxed- 最宽松

#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> x(0), y(0);
void relaxed_example() {
    // 线程1
    std::thread t1([](){
        x.store(1, std::memory_order_relaxed);  // 可能重排
        y.store(1, std::memory_order_relaxed);  // 可能重排
    });
    // 线程2
    std::thread t2([](){
        int r1 = y.load(std::memory_order_relaxed);  // 可能看到y=1但x=0
        int r2 = x.load(std::memory_order_relaxed);
        std::cout << "y=" << r1 << ", x=" << r2 << std::endl;
    });
    t1.join(); t2.join();
}

适用场景:

// 计数器 - 顺序不重要,只要原子就行
std::atomic<int> counter(0);
void increment() {
    counter.fetch_add(1, std::memory_order_relaxed);
}

3.2memory_order_acquire和memory_order_release- 配对使用

#include <atomic>
#include <thread>
#include <cassert>
std::atomic<bool> flag{false};
int data = 0;
void producer() {
    data = 42;                                  // 1. 准备数据
    flag.store(true, std::memory_order_release); // 2. 发布标志(保证1在2之前)
}
void consumer() {
    while (!flag.load(std::memory_order_acquire)) { // 3. 获取标志(保证4在3之后)
        // 等待
    }
    assert(data == 42);  // 4. 读取数据(这里一定能看到42!)
}

屏障效果图示:

线程A (Producer)        线程B (Consumer)
data = 42
    ↓ (release屏障)
flag = true
                ─────→   while(!flag)
                            ↓ (acquire屏障)
                         assert(data==42) ✓

3.3memory_order_acq_rel- 读改写操作

class SpinLock {
    std::atomic<bool> locked{false};
public:
    void lock() {
        // 期望locked=false,设置locked=true
        // 需要同时保证acquire和release语义
        while (locked.exchange(true, std::memory_order_acq_rel)) {
            // 自旋等待
        }
    }
    void unlock() {
        locked.store(false, std::memory_order_release);
    }
};

为什么需要acq_rel:

std::atomic<int> shared{0};
int normal_data = 0;
void thread_work() {
    // 修改前操作
    normal_data = 100;
    // CAS操作:既有读又有写
    int expected = 0;
    if (shared.compare_exchange_strong(expected, 1, 
                                      std::memory_order_acq_rel)) {
        // 成功:相当于acquire,能看到之前的修改
        // 同时:相当于release,保证之前的修改对其他线程可见
    }
}

3.4memory_order_seq_cst- 顺序一致性(默认)

std::atomic<int> x{0}, y{0};
void sequential_example() {
    std::thread t1([](){
        x.store(1, std::memory_order_seq_cst);  // 1
        y.store(1, std::memory_order_seq_cst);  // 2
    });
    std::thread t2([](){
        int r1 = y.load(std::memory_order_seq_cst);  // 3
        int r2 = x.load(std::memory_order_seq_cst);  // 4
        // 所有线程看到相同的操作顺序
        // 可能的顺序:1→2→3→4 或 1→3→2→4 等
        // 但所有线程对顺序的理解一致
    });
    t1.join(); t2.join();
}

4.在shared_ptr中的实际应用

您代码中的内存序分析:

void release() {
    if (ref_count && ref_count->fetch_sub(1, std::memory_order_acq_rel) == 1) {
        delete ptr;
        delete ref_count;
    }
}
// 为什么用 memory_order_acq_rel?

详细解释:

class shared_ptr {
    void release() {
        // fetch_sub是读-修改-写操作:
        // 1. 读取当前值 (需要acquire语义)
        // 2. 减去1
        // 3. 写回新值 (需要release语义)
        // 使用acq_rel确保:
        if (ref_count && 
            ref_count->fetch_sub(1, std::memory_order_acq_rel) == 1) {
            // ↓ 这个delete操作必须看到ptr的所有修改
            delete ptr;     // 需要acquire保证看到完整对象状态
            delete ref_count;
            // 同时,在delete之前的所有对象修改
            // 必须对其他线程可见 (release语义)
        }
    }
};

5.内存序的层次关系

从弱到强的约束:

relaxed (最弱)
   ↓
consume
   ↓  
acquire/release
   ↓
acq_rel
   ↓
seq_cst (最强)

6.实际编程建议

6.1什么时候用什么内存序

// 情况1:简单计数器
std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed);  // ✅
// 情况2:标志位同步
std::atomic<bool> ready{false};
int data;
// 生产者
data = compute_data();
ready.store(true, std::memory_order_release);     // ✅
// 消费者  
while (!ready.load(std::memory_order_acquire)) {} // ✅
use_data(data);
// 情况3:复杂的同步逻辑(不确定时)
std::atomic<int> state{0};
state.compare_exchange_strong(old_val, new_val, 
                             std::memory_order_seq_cst);  // ✅ 安全第一

6.2实用示例:无锁队列

template<typename T>
class LockFreeQueue {
    struct Node {
        T data;
        std::atomic<Node*> next;
    };
    std::atomic<Node*> head, tail;
public:
    void push(const T& value) {
        Node* new_node = new Node{value, nullptr};
        Node* old_tail = tail.load(std::memory_order_acquire);
        while (!tail.compare_exchange_weak(old_tail, new_node,
                                          std::memory_order_acq_rel,
                                          std::memory_order_acquire)) {
            // CAS失败,重试
        }
        // 链接节点
        old_tail->next.store(new_node, std::memory_order_release);
    }
};

7.测试和验证

内存序错误的检测:

#include <atomic>
#include <thread>
#include <cassert>
std::atomic<int> x{0}, y{0};
int r1, r2;
void memory_order_test() {
    std::thread t1([](){
        x.store(1, std::memory_order_relaxed);
        y.store(1, std::memory_order_relaxed);
    });
    std::thread t2([](){
        r1 = y.load(std::memory_order_relaxed);
        r2 = x.load(std::memory_order_relaxed);
    });
    t1.join(); t2.join();
    // 在relaxed顺序下,可能发生:
    // r1 == 1 (y已设置) 但 r2 == 0 (x还未设置)
    std::cout << "Result: r1=" << r1 << ", r2=" << r2 << std::endl;
}

8.总结

关键要点:

使用建议:

您的shared_ptr实现中使用的 memory_order_acq_rel 是非常合适的,它确保了在释放资源时能够看到对象的完整状态,同时也保证了对象修改对其他线程的可见性。

到此这篇关于C++内存序的操作方法的文章就介绍到这了,更多相关C++内存序内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文