1. 原子操作的必要性与基础概念
在现代多线程编程中,数据竞争(data race)是最常见的并发问题之一。当多个线程同时访问同一内存位置,且至少有一个线程在写入时,如果没有适当的同步机制,就会导致未定义行为。传统解决方案如互斥锁(mutex)虽然有效,但会带来性能开销和死锁风险。
C++11引入的原子类型(atomic types)提供了一种无锁(lock-free)编程的解决方案。原子操作保证了对特定内存地址的读写操作是不可分割的(indivisible),即其他线程无法观察到操作过程中的中间状态。这种特性使得原子操作成为构建高效并发程序的基础工具。
原子操作的核心特性包括:
- 原子性(Atomicity):操作要么完全执行,要么完全不执行,不会出现部分完成的状态
- 可见性(Visibility):一个线程对原子变量的修改会立即对其他线程可见
- 顺序性(Ordering):通过内存顺序(memory order)参数控制操作之间的执行顺序
2. std::atomic模板类的深度解析
2.1 基本用法与特化类型
std::atomic是一个模板类,可以对整数类型、指针类型以及用户定义的可平凡复制(trivially copyable)类型进行原子封装。标准库为常用类型提供了特化版本:
cpp复制std::atomic<int> atomicInt; // 原子整型
std::atomic<bool> atomicBool; // 原子布尔
std::atomic<float> atomicFloat; // C++20起支持浮点原子类型
原子类型支持的基本操作包括:
- load():原子读取当前值
- store():原子写入新值
- exchange():原子交换值并返回旧值
- compare_exchange_weak/strong:比较并交换(CAS)操作
2.2 原子算术运算
对于整数类型的原子变量,std::atomic提供了一系列原子算术运算:
cpp复制std::atomic<int> counter(0);
counter.fetch_add(1); // 原子加1,相当于++counter
counter.fetch_sub(1); // 原子减1,相当于--counter
counter.fetch_and(0xFF); // 原子位与操作
counter.fetch_or(0x01); // 原子位或操作
这些操作在多线程计数器、标志位处理等场景中非常有用。值得注意的是,这些操作都有对应的运算符重载版本:
cpp复制counter += 1; // 等价于fetch_add
counter -= 1; // 等价于fetch_sub
2.3 内存模型的影响
原子操作的默认内存顺序是memory_order_seq_
