C++并发编程中的原子操作、标志、栅栏与volatile关键字详解
立即解锁
发布时间: 2025-08-16 01:21:07 阅读量: 2 订阅数: 24 


C++编程语言第四版:核心概念与技术
### C++并发编程中的原子操作、标志、栅栏与volatile关键字详解
#### 1. 原子操作概述
在并发编程中,锁是常用的同步机制,但锁会带来一些问题,如死锁和饥饿。而锁无关编程(lock-free programming)则是一种不使用显式锁来编写并发程序的技术。它依赖于硬件直接支持的原语操作来避免小对象(通常是单字或双字)的数据竞争。这些不会出现数据竞争的原语操作,常被称为原子操作。原子操作可用于实现更高级的并发机制,如锁、线程和无锁数据结构。
锁无关编程除了简单的原子计数器外,通常是专家领域。它不仅要求对语言机制有深入理解,还需要详细了解特定的机器架构和一些专门的实现技术。
锁无关技术相对于基于锁的技术的主要逻辑优势在于,经典的锁问题(如死锁和饥饿)不会发生。对于每个原子操作,即使其他线程竞争访问原子对象,也能保证每个线程最终(通常很快)取得进展。此外,锁无关技术可能比基于锁的替代方案快得多。
#### 2. 标准原子类型和操作
标准原子类型和操作提供了一种可移植的方式来表达无锁代码,避免了传统上依赖汇编代码或特定系统原语的方式。这是C和C++在系统编程中增加可移植性和相对易理解性支持的又一步。
##### 2.1 同步操作和内存顺序
同步操作决定了一个线程何时能看到另一个线程的效果,以及什么被认为在其他事情之前发生。在同步操作之间,编译器和处理器可以在遵守语言语义规则的前提下自由地重新排序代码,这主要影响性能。同步操作包括消费操作、获取操作、释放操作或获取和释放操作。
- **获取操作**:其他处理器会在任何后续操作的效果之前看到其效果。
- **释放操作**:其他处理器会在该操作本身的效果之前看到每个先前操作的效果。
- **消费操作**:是获取操作的一种较弱形式,其他处理器会在任何后续操作的效果之前看到其效果,但不依赖于消费操作值的效果可能会在消费操作之前发生。
标准内存顺序如下:
```cpp
enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
};
```
这些枚举表示的含义如下:
| 内存顺序 | 含义 |
| --- | --- |
| memory_order_relaxed | 无操作对内存进行排序 |
| memory_order_release, memory_order_acq_rel, memory_order_seq_cst | 存储操作对受影响的内存位置执行释放操作 |
| memory_order_consume | 加载操作对受影响的内存位置执行消费操作 |
| memory_order_acquire, memory_order_acq_rel, memory_order_seq_cst | 加载操作对受影响的内存位置执行获取操作 |
下面是一个使用原子加载和存储来表达宽松内存顺序的例子:
```cpp
// thread 1:
r1 = y.load(memory_order_relaxed);
x.store(r1,memory_order_relaxed);
// thread 2:
r2 = x.load(memory_order_relaxed);
y.store(42,memory_order_relaxed);
```
这种宽松内存顺序可能会导致 `r2==42`,看起来就像线程2中的时间倒流了。
宽松内存模型通常不适合直接用于应用程序编程,它是比一般无锁编程更专业的任务,主要由操作系统内核、设备驱动程序和虚拟机实现者使用。为了对具有宽松内存模型的架构进行显著优化,标准提供了 `[[carries_dependency]]` 属性来跨函数调用传递内存顺序依赖。
##### 2.2 原子类型
原子类型是 `atomic` 模板的特化。对原子类型对象的操作是原子的,即由单个线程执行,不受其他线程干扰。原子操作非常简单,如加载、存储、交换、递增等,因为硬件只能直接处理简单操作。
以下是 `atomic<T>` 的一些常见操作:
| 操作 | 描述 |
| --- | --- |
| `atomic x;` | x 未初始化 |
| `atomic x {};` | 默认构造函数:`x.val=T{};` 常量表达式 |
| `atomic x {t};` | 构造函数:`x.val=t;` 常量表达式 |
| `x=t` | 赋值:`x.val=t` |
| `t=x` | 隐式转换为 T:`t=x.val` |
| `x.is_lock_free()` | 检查对 x 的操作是否无锁 |
| `x.store(t)` | `x.val=t` |
| `x.store(t,order)` | `x.val=t;` 内存顺序为 order |
| `t=x.load()` | `t=x.val` |
| `t=x.load(order)` | `t=x.val;` 内存顺序为 order |
| `t2=x.exchange(t)` | 交换 x 和 t 的值,`t2` 是 x 的前一个值 |
| `t2=x.exchange(t,order)` | 交换 x 和 t 的值,内存顺序为 order,`t2` 是 x 的前一个值 |
| `b=x.compare_exchange_weak(rt,t)` | 如果 `b=(x.val==rt)`,则 `x.val=t`,否则 `rt=x.val` |
| `b=x.compare_exchange_strong(rt,t)` | 类似 `compare_exchange_weak`,但强版本不太可能因虚假原因失败 |
原子操作的一些特点和注意事项:
- 原子设施设计用于映射到简单内置类型的类型。如果 T 对象很大,`atomic<T>` 可能会使用锁来实现。
- 模板参数类型 T 必须是可平凡复制的(
0
0
复制全文
相关推荐







