面试题:请介绍 C++ 的 6 种内存序?

在 C++ 中,内存序(Memory Order)是与原子操作和多线程编程相关的重要概念。C++11 引入了 std::memory_order 枚举类型,用于指定原子操作的内存顺序。内存序决定了原子操作在多线程环境中的可见性和顺序性。

以下是 C++ 中的 6 种内存序及其含义:


1. memory_order_relaxed

  • 含义:最宽松的内存序,只保证原子操作的原子性,不保证顺序性和可见性。
  • 适用场景
    • 适用于不需要同步的场景,例如计数器或标志位。
  • 示例
  std::atomic<int> counter{0};
  counter.fetch_add(1, std::memory_order_relaxed);

2. memory_order_consume

  • 含义:依赖于当前加载操作的数据的后续操作不会被重排序到加载操作之前。
  • 适用场景
    • 用于数据依赖的场景,例如指针的加载。
  • 注意
    • 由于实现复杂且容易出错,大多数编译器将其视为 memory_order_acquire
  • 示例
  std::atomic<int*> ptr{nullptr};
  int* p = ptr.load(std::memory_order_consume);
  if (p) {
      int value = *p; // 依赖于 p 的操作
  }

3. memory_order_acquire

  • 含义
    • 当前线程中,所有后续的读写操作不会被重排序到当前加载操作之前。
    • 确保当前线程能看到其他线程在释放操作之前的所有写入。
  • 适用场景
    • 用于加载操作,确保后续操作能看到正确的数据。
  • 示例
  std::atomic<bool> flag{false};
  int data = 0;

  // 线程 1
  data = 42;
  flag.store(true, std::memory_order_release);

  // 线程 2
  while (!flag.load(std::memory_order_acquire)) {}
  std::cout << data; // 保证看到 data = 42

4. memory_order_release

  • 含义
    • 当前线程中,所有之前的读写操作不会被重排序到当前存储操作之后。
    • 确保当前线程的写入对其他线程的获取操作可见。
  • 适用场景
    • 用于存储操作,确保之前的操作对其他线程可见。
  • 示例
  std::atomic<bool> flag{false};
  int data = 0;

  // 线程 1
  data = 42;
  flag.store(true, std::memory_order_release); // 确保 data = 42 对其他线程可见

  // 线程 2
  while (!flag.load(std::memory_order_acquire)) {}
  std::cout << data; // 保证看到 data = 42

5. memory_order_acq_rel

  • 含义
    • 结合了 memory_order_acquirememory_order_release 的特性。
    • 对于当前操作,既保证获取语义,又保证释放语义。
  • 适用场景
    • 用于读-修改-写操作(如 fetch_addexchange)。
  • 示例
  std::atomic<int> counter{0};
  counter.fetch_add(1, std::memory_order_acq_rel);

6. memory_order_seq_cst

  • 含义
    • 最严格的内存序,保证所有线程看到的操作顺序是一致的。
    • 所有 memory_order_seq_cst 操作会形成一个全局顺序。
  • 适用场景
    • 需要强一致性的场景,例如锁的实现。
  • 性能
    • 由于严格的顺序性,性能开销较大。
  • 示例
  std::atomic<bool> flag{false};
  int data = 0;

  // 线程 1
  data = 42;
  flag.store(true, std::memory_order_seq_cst);

  // 线程 2
  while (!flag.load(std::memory_order_seq_cst)) {}
  std::cout << data; // 保证看到 data = 42

7. 内存序的总结

内存序特性适用场景
memory_order_relaxed只保证原子性,不保证顺序性和可见性。计数器、标志位等简单场景。
memory_order_consume保证数据依赖的顺序性。数据依赖的场景(较少使用)。
memory_order_acquire保证后续操作不会重排序到加载操作之前,确保看到释放操作之前的写入。加载操作,确保数据可见性。
memory_order_release保证之前操作不会重排序到存储操作之后,确保写入对获取操作可见。存储操作,确保数据可见性。
memory_order_acq_rel结合获取和释放语义,适用于读-修改-写操作。读-修改-写操作。
memory_order_seq_cst保证全局一致性,所有线程看到的操作顺序一致。需要强一致性的场景(如锁的实现)。

8. 示例:内存序的应用

以下是一个使用不同内存序的示例,展示了如何实现线程同步:

#include <iostream>
#include <atomic>
#include <thread>

std::atomic<int> data{0};
std::atomic<bool> ready{false};

void producer() {
    data.store(42, std::memory_order_relaxed); // 宽松存储
    ready.store(true, std::memory_order_release); // 释放存储
}

void consumer() {
    while (!ready.load(std::memory_order_acquire)) {} // 获取加载
    std::cout << "Data: " << data.load(std::memory_order_relaxed) << std::endl; // 宽松加载
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);

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

    return 0;
}

9. 总结

  • C++ 提供了 6 种内存序,用于控制原子操作在多线程环境中的可见性和顺序性。
  • 根据场景选择合适的内存序,可以在保证正确性的同时优化性能。
  • memory_order_seq_cst 是最严格的内存序,适用于需要强一致性的场景;memory_order_relaxed 是最宽松的内存序,适用于简单场景。
THE END
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容