走进C++11(四十)最宽松的顺序 memory_order_relaxed 内存模型(三)

本文深入解析了C++中的memory_order_relaxed内存模型,展示了它如何在不提供同步保障的前提下支持原子操作,如计数器自增。通过实例说明了其应用场景,以及何时选择这种宽松的内存顺序。

关注公众号获取更多信息:

 

之前讲的都是理论相关的,下面详细讲一下我们现实中会使用到的内存模型。今天是最简单也是最宽松的内存模型----memory_order_relaxed 

 

带标签 memory_order_relaxed 的原子操作无同步操作;它们不会在同时的内存访问间强加顺序。它们只保证原子性和修改顺序一致性。

例如,

 

对于最初为零的 x 和 y ,// 线程 1 :r1 = y.load(std::memory_order_relaxed); // Ax.store(r1, std::memory_order_relaxed); // B// 线程 2 :r2 = x.load(std::memory_order_relaxed); // Cy.store(42, std::memory_order_relaxed); // D

 

 

 

 

允许产生结果 r1 == 42 && r2 == 42 ,因为即使线程 1 中 A 先序于 B 且线程 2 中 C 先序于 D ,却没有制约避免 y 的修改顺序中 D 先出现于 A ,而 x 的修改顺序中 B 先出现于 C 。D 在 y 上的副效应,可能可见于线程 1 中的加载 A ,同时 B 在 x 上的副效应,可能可见于线程 2 中的加载 C 。

 

即使使用宽松内存模型,也不允许“无中生有”的值循环地依赖于其各自的计算,例如,对于最初为零的 x 和 y ,

// 线程1:
r1 = x.load(std::memory_order_relaxed);
if (r1 == 42) y.store(r1, std::memory_order_relaxed);
// 线程2:
r2 = y.load(memory_order_relaxed);
if (r2 == 42) x.store(42, std::memory_order_relaxed);

不允许产生结果 r1 == 42 && r2 == 42 ,因为存储 42 于 y 只在存储 42 于 x 后有可能,这又循环依赖于存储 42 于 y 。注意 C++14 前规范曾在技术上允许,但不推荐实现者如此实现。

(C++14 起)

 

宽松内存顺序的典型使用是计数器自增,例如 std::shared_ptr 的引用计数器,因为这只要求原子性,但不要求顺序或同步(注意 std::shared_ptr 计数器自减要求与析构函数进行获得释放同步)

 

下面举个例子:

 

#include <vector>#include <iostream>#include <thread>#include <atomic> std::atomic<int> cnt = {0}; void f(){    for (int n = 0; n < 1000; ++n) {        cnt.fetch_add(1, std::memory_order_relaxed);    }} int main(){    std::vector<std::thread> v;    for (int n = 0; n < 10; ++n) {        v.emplace_back(f);    }    for (auto& t : v) {        t.join();    }    std::cout << "Final counter value is " << cnt << '\n';}

输出:

 

Final counter value is 10000

 

那么在什么情景下使用memory_order_relaxed呢?

 

由于Relaxed ordering 仅仅保证load()store()是原子操作,除此之外,不提供任何跨线程的同步。

 

如果某个操作只要求是原子操作,除此之外,不需要其它同步的保障,就可以使用 Relaxed ordering。程序计数器是一种典型的应用场景:

 


 
#include <cassert>#include <vector>#include <iostream>#include <thread>#include <atomic>std::atomic<int> cnt = {0};void f(){  for (int n = 0; n < 1000; ++n) {    cnt.fetch_add(1, std::memory_order_relaxed);  }}int main(){  std::vector<std::thread> v;  for (int n = 0; n < 10; ++n) {      v.emplace_back(f);    }  for (auto& t : v) {    t.join();  }  assert(cnt == 10000);    // never failed  return 0;}
<think>我们详细讲解C++中的内存顺序memory order)参数,包括`std::memory_order_relaxed`、`std::memory_order_acquire`、`std::memory_order_release`等。 ### 内存顺序Memory Order)详解 在C++中,`std::atomic`操作允许指定内存顺序参数,以控制原子操作的内存可见性和顺序性。这些参数允许开发者在性能和控制之间进行权衡。以下是常见的`std::memory_order`枚举值及其使用场景: #### 1. `std::memory_order_relaxed` - **特点**:仅保证原子操作的原子性,不提供任何顺序保证(即编译器和处理器可以重排指令)。 - **适用场景**:计数器等不需要同步的场景,性能高。 ```cpp std::atomic<int> counter(0); void increment() { counter.fetch_add(1, std::memory_order_relaxed); } ``` #### 2. `std::memory_order_acquire` - **特点**:用于读操作(load),保证该操作之后的读写操作不会被重排到此操作之前(即获得同步)。 - **适用场景**:与`release`配对,用于实现锁或同步点。 ```cpp std::atomic<bool> flag(false); int data = 0; void reader() { while (!flag.load(std::memory_order_acquire)) {} // 等待flag被设置 // 保证在此处看到writer线程在设置flag之前的所有写入 std::cout << data << std::endl; // 输出42 } void writer() { data = 42; flag.store(true, std::memory_order_release); // 设置flag,并保证之前的写入对acquire可见 } ``` #### 3. `std::memory_order_release` - **特点**:用于写操作(store),保证该操作之前的读写操作不会被重排到此操作之后(即释放同步)。 - **适用场景**:与`acquire`配对,用于实现锁或同步点。 #### 4. `std::memory_order_acq_rel` - **特点**:同时具有acquire和release语义,适用于读-修改-写操作(如`fetch_add`)。 - **适用场景**:需要同时进行获取和释放的原子操作,例如在自旋锁中。 #### 5. `std::memory_order_seq_cst` - **特点**:顺序一致性(Sequential Consistency),默认内存顺序。保证所有线程看到的操作顺序一致,且所有操作不可重排。 - **适用场景**:需要强一致性的场景,但性能较低。 #### 6. `std::memory_order_consume`(C++11起已不推荐使用) - **特点**:数据依赖顺序,保证依赖该原子操作的后续操作不会被重排到前面。但在C++17中不鼓励使用,因为其语义复杂且实现困难。 ### 内存顺序配对示例 - **Acquire-Release配对**:常见的同步模式,用于临界区的保护。 ```cpp std::atomic<bool> ready(false); int data = 0; void producer() { data = 100; // 非原子操作 ready.store(true, std::memory_order_release); // 释放操作:保证之前的写入对消费者可见 } void consumer() { while (!ready.load(std::memory_order_acquire)); // 获取操作:等待生产者释放 assert(data == 100); // 一定为true } ``` ### 顺序一致性(seq_cst)与Acquire-Release对比 - **顺序一致性(seq_cst)**:所有线程看到的操作顺序与全局顺序一致,但性能开销较大。 - **Acquire-Release**:仅保证有同步关系的线程之间的可见性,性能更好。 ### 何时使用何种内存顺序? - 默认情况下使用`std::memory_order_seq_cst`(安全但较慢)。 - 在性能关键且同步关系明确时,使用`acquire-release`(更高效)。 - 计数器等独立原子变量使用`relaxed`。 §§相关问题§§: 1. 什么是内存重排(Memory Reordering)?为什么需要内存顺序控制? 2. `std::memory_order_consume`为什么被弃用? 3. 如何用`std::atomic_flag`和`memory_order`实现一个自旋锁? 4. 在无锁编程中,`compare_exchange_weak`通常与哪种内存顺序一起使用? </think> ### `std::memory_order` 详解:用法与场景 `std::memory_order` 是 C++ 原子操作中控制内存访问顺序的关键参数,用于平衡性能与同步需求。以下是主要枚举值及其典型用法: --- #### 1. **`std::memory_order_relaxed`** **特性**: - 仅保证操作的原子性,**不提供任何同步或顺序保证** - 编译器和处理器可以自由重排指令 **适用场景**:计数器、统计量等无需同步的场景 ```cpp std::atomic<int> counter(0); void increment() { counter.fetch_add(1, std::memory_order_relaxed); // 只保证原子性 } ``` --- #### 2. **`std::memory_order_acquire`** **特性**: - 用于 **读操作**(如 `load()`) - 保证后续读写操作不会重排到此操作之前 - 与 `release` 形成同步关系 **适用场景**:锁获取、数据依赖读取 ```cpp std::atomic<bool> ready(false); int data = 0; void consumer() { while (!ready.load(std::memory_order_acquire)) {} // 等待生产者 std::cout << data; // 保证看到 data = 42 } void producer() { data = 42; // 非原子写入 ready.store(true, std::memory_order_release); // 同步点 } ``` --- #### 3. **`std::memory_order_release`** **特性**: - 用于 **写操作**(如 `store()`) - 保证前面的读写操作不会重排到此操作之后 - 与 `acquire` 形成同步关系 **适用场景**:锁释放、发布共享数据 --- #### 4. **`std::memory_order_acq_rel`** **特性**: - 同时包含 `acquire` 和 `release` 语义 - 用于 **读-修改-写操作**(如 `exchange()`, `fetch_add()`) **适用场景**:自旋锁实现 ```cpp std::atomic_flag lock = ATOMIC_FLAG_INIT; void lock_spinlock() { while (lock.test_and_set(std::memory_order_acq_rel)) {} // 获取锁 } void unlock_spinlock() { lock.clear(std::memory_order_release); // 释放锁 } ``` --- #### 5. **`std::memory_order_seq_cst`** **特性**: - **默认内存顺序**(强一致性) - 全局顺序一致性:所有线程看到相同的操作顺序 - 性能开销大 **适用场景**:需要严格顺序的跨线程交互 ```cpp std::atomic<bool> x(false), y(false); // 线程1 x.store(true, std::memory_order_seq_cst); // 全局可见顺序 // 线程2 y.store(true, std::memory_order_seq_cst); // 线程3 if (x.load(std::memory_order_seq_cst) && !y.load(std::memory_order_seq_cst)) { // 不可能进入此分支(顺序一致性保证) } ``` --- #### 6. **`std::memory_order_consume`**(C++17 起不推荐使用) **特性**: - 弱于 `acquire`,仅保证数据依赖操作的顺序 - 实际应用中通常用 `acquire` 替代 --- ### 内存顺序配对规则 | 操作类型 | 推荐内存顺序 | |-------------------------|----------------------------------| | 独立原子写 | `relaxed` 或 `release` | | 独立原子读 | `relaxed` 或 `acquire` | | 读-修改-写 | `acq_rel` 或 `seq_cst` | | 跨线程同步 | `release`(写) + `acquire`(读)| | 全序约束(如互斥锁实现)| `seq_cst` | --- ### 关键概念图解 ``` Thread A: [写非原子变量] → store(..., release) → 同步边界 → Thread B: load(..., acquire) → [读非原子变量] ``` - **`release`** 前的所有读写对 **`acquire`** 后的操作可见 - 重排禁止:`release` 前操作不后移,`acquire` 后操作不前移
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值