返回

C++ 多线程:探索内存模型的细微之处

后端

C++ 多线程:内存模型 (std::memory_order)

概念

在 C++11 标准原子库中 (std::atomic),大多数函数都接受一个参数:std::memory_order。此参数指定了原子操作在执行时的内存语义行为。在多线程环境中,了解和正确使用 std::memory_order 至关重要,因为它有助于确保线程之间的正确同步和数据一致性。

std::memory_order 枚举包含以下值,指定了不同的内存语义:

  • memory_order_relaxed:不提供任何同步保证。
  • memory_order_consume:仅提供对当前线程的可见性保证。
  • memory_order_acquire:仅提供对其他线程的可见性保证。
  • memory_order_release:使对其他线程可见。
  • memory_order_acq_rel:既获取又释放,提供对所有线程的可见性和同步。
  • memory_order_seq_cst:最严格的内存语义,提供顺序一致性。

理解不同内存语义

  • Relaxed: 没有任何同步保证,允许编译器和处理器在执行指令时进行重新排序和优化。
  • Consume: 保证当前线程可以看到由原子操作写入的值,但其他线程可能仍未看到。
  • Acquire: 保证其他线程可以看到由原子操作写入的值,但当前线程可能仍未看到。
  • Release: 保证对当前线程可见的写入对其他线程也可见。
  • Acq/Rel: 既是 AcquireRelease,保证所有线程都可以看到写入,并且在所有线程中按照顺序执行。
  • Seq/Cst: 最严格的内存语义,保证写入按顺序执行,并且对所有线程始终可见。

选择合适的内存语义

选择合适的 std::memory_order 对于确保多线程程序的正确性和健壮性至关重要。一般指南包括:

  • 对于临时数据或不会被其他线程访问的数据,可以使用 Relaxed
  • 对于线程间通信,如信号量或锁,应使用 Acquire/Release
  • 对于需要严格有序执行的代码块,应使用 Seq/Cst

示例

以下是一个示例,说明如何使用不同的 std::memory_order

std::atomic<int> shared_value = 0;

void thread1() {
  shared_value.store(1, std::memory_order_release);
}

void thread2() {
  while (shared_value.load(std::memory_order_acquire) == 0) {
    // 等待线程 1 设置 shared_value
  }
}

在这个示例中,thread1 使用 std::memory_order_release 将值存储到 shared_value,这将确保对其他线程可见。thread2 使用 std::memory_order_acquire 加载 shared_value,这将确保它可以看到 thread1 存储的值。

结论

理解和正确使用 std::memory_order 在 C++ 多线程编程中至关重要。通过仔细选择内存语义,开发人员可以确保线程之间的正确同步和数据一致性,从而构建健壮且可扩展的多线程应用程序。