atomic 是什么及相关操作介绍

1. atomic 是什么

在 C++ 中,std::atomic 是 C++11 引入的模板类,位于 <atomic> 头文件中,用于实现线程安全的原子操作。原子操作是指在多线程环境中不可分割的操作,保证操作要么完全完成,要么完全不执行,避免数据竞争(data race)。

  • 作用
    • 提供线程安全的变量读写,防止多线程并发访问导致的数据不一致。
    • 避免使用锁(如互斥锁),减少锁竞争和上下文切换开销。
    • 支持基本数据类型(如 intbool、指针)和其他自定义类型的原子操作。
  • 典型场景
    • 多线程计数器。
    • 无锁数据结构(如无锁队列)。
    • 标志位或状态变量的线程安全更新。

2. std::atomic 的核心特性

  • 原子性:操作不可中断,确保线程安全。
  • 内存序:通过 std::memory_order 控制内存访问顺序,优化性能和一致性。
  • 支持类型
    • 内置类型:std::atomic<int>std::atomic<bool> 等。
    • 指针类型:std::atomic<T*>
    • 自定义类型:需满足“平凡可复制”条件(Trivially Copyable)。

3. 主要操作

以下是 std::atomic 的常用操作,假设 std::atomic<T> var 是一个原子变量:

(1) 基本读写操作

  • load:读取原子变量的值。
    1
    T value = var.load(std::memory_order_acquire);
    • 获取 var 的当前值,保证读取最新值。
  • store:写入新值到原子变量。
    1
    var.store(new_value, std::memory_order_release);
    • new_value 写入 var,确保对其他线程可见。
  • exchange:原子地替换值并返回旧值。
    1
    T old_value = var.exchange(new_value, std::memory_order_acq_rel);
    • var 替换为 new_value,返回替换前的值。

(2) 比较并交换(CAS)

  • compare_exchange_strong
    1
    2
    T expected = current_value;
    bool success = var.compare_exchange_strong(expected, desired, std::memory_order_acq_rel);
    • 如果 var == expected,则将 var 替换为 desired,返回 true
    • 否则,expected 被更新为 var 的当前值,返回 false
    • 适合无锁算法,处理竞争。
  • compare_exchange_weak
    • 类似 compare_exchange_strong,但在某些架构上可能因伪失败(spurious failure)返回 false,需配合循环重试。
      1
      2
      3
      4
      T expected = current_value;
      while (!var.compare_exchange_weak(expected, desired, std::memory_order_acq_rel)) {
      // 重试
      }

(3) 算术和位操作

仅对 std::atomic 的整型或指针类型支持:

  • fetch_add / fetch_sub:原子加/减,返回旧值。
    1
    T old = var.fetch_add(1, std::memory_order_relaxed); // var += 1
  • fetch_and / fetch_or / fetch_xor:原子位操作。
    1
    T old = var.fetch_and(mask, std::memory_order_relaxed); // var &= mask
  • **operator++ / operator–**:原子自增/自减。
    1
    var++; // 等价于 fetch_add(1) + 1

(4) 其他操作

  • is_lock_free:检查原子操作是否无锁。
    1
    bool is_lock_free = var.is_lock_free();
    • 返回 true 表示操作由硬件支持,false 表示可能使用内部锁。
  • wait / notify_one / notify_all(C++20):
    • 等待原子变量值变化(类似条件变量)。
      1
      2
      var.wait(old_value, std::memory_order_acquire); // 等待 var 变化
      var.notify_one(); // 唤醒一个等待线程

4. 内存序(Memory Order)

std::atomic 操作支持指定内存序,控制多线程间的内存访问顺序,平衡正确性和性能:

  • **std::memory_order_relaxed**:
    • 仅保证原子性,不保证内存访问顺序。
    • 适合独立操作,如计数器。
  • **std::memory_order_acquire**:
    • 确保操作后的读取看到其他线程的 release 操作结果。
    • 用于消费者线程。
  • **std::memory_order_release**:
    • 确保操作前的写入对其他线程的 acquire 操作可见。
    • 用于生产者线程。
  • **std::memory_order_acq_rel**:
    • 结合 acquirerelease,用于读写操作(如 exchange)。
  • **std::memory_order_seq_cst**(默认):
    • 提供最强的顺序一致性,保证所有线程看到一致的操作顺序。
    • 开销较大,但最安全。

5. 代码示例

以下是一个使用 std::atomic 实现线程安全计数器的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <atomic>
#include <iostream>
#include <thread>

std::atomic<int> counter(0); // 原子计数器

void increment(int n) {
for (int i = 0; i < n; ++i) {
counter.fetch_add(1, std::memory_order_relaxed);
}
}

int main() {
std::thread t1(increment, 1000);
std::thread t2(increment, 1000);
t1.join();
t2.join();
std::cout << "Final counter: " << counter.load() << "\n"; // 输出 2000
return 0;
}
  • 说明:多个线程并发调用 fetch_add,计数器保持线程安全。

6. 注意事项

  • 性能
    • 原子操作通常比锁更快,但 CAS 重试可能导致性能波动。
    • 选择合适的内存序(如 relaxed)可优化性能。
  • 限制
    • 仅支持简单数据类型或指针,复杂类型需自定义无锁机制。
    • compare_exchange_weak 可能因伪失败需重试,适合循环场景。
  • ABA 问题
    • 在 CAS 操作中,值可能被修改后恢复为原值,导致误判。
    • 解决方法:使用版本计数或双字 CAS。
  • 适用场景
    • 适合高并发、低冲突场景,如无锁队列、计数器、状态标志。
    • 高冲突场景可能仍需锁机制。

7. 总结

  • **std::atomic**:提供线程安全的原子操作,避免锁开销。
  • 核心操作loadstoreexchangecompare_exchange_strong/weak、算术/位操作。
  • 内存序:通过 memory_order 控制同步和性能。
  • 应用:无锁数据结构、线程安全计数器、标志位管理。