std::atomic 是 C++11 引入的原子操作库,定义在 <atomic> 头文件中,核心作用是提供无锁(Lock-Free)的线程安全访问,避免多线程并发修改共享变量时出现的数据竞争(Data Race)。它底层依赖 CPU 原子指令(如 CAS、Load/Store 屏障),无需显式加锁(如互斥锁、自旋锁),就能保证单个操作的原子性、可见性和有序性,是实现高效并发的基础工具。

在多线程环境中,普通共享变量的读写可能存在 “数据竞争”—— 例如线程 A 写入 i = 1,线程 B 读取 i,由于 CPU 指令重排序、缓存一致性等问题,线程 B 可能读取到 “中间值”(尤其是非单个 CPU 指令能完成的操作,如 i++)。

传统解决方案是用锁(如 std::mutex)保护临界区,但锁会带来上下文切换开销;而 std::atomic 直接通过 CPU 原子指令实现,无锁开销、延迟更低,适用于 “单个变量的简单同步” 场景(如计数器、标志位)。

std::atomic 保证三大核心特性(并发安全的基石):

  1. 原子性:操作是 “不可分割” 的,不会被其他线程打断(如 atomic_var++ 不会出现 “一半执行、一半被抢占” 的情况);
  2. 可见性:一个线程对原子变量的修改,会立即被其他线程感知(避免缓存导致的 “脏读”);
  3. 有序性:默认禁止编译器和 CPU 对原子操作进行重排序(可通过内存序参数灵活控制)。    

接下来我们来讲一下其用法

基本用法:原子变量的定义与操作

1. 原子变量的定义

std::atomic 是模板类,支持大部分基础数据类型(boolcharintlong、指针等),也支持用户自定义的 “可平凡复制” 类型(Trivially Copyable,即能通过 memcpy 复制的类型,如不含指针的结构体)。

常见定义方式:

cpp

#include <atomic>
#include <thread>
#include <iostream>

// 1. 基础类型原子变量
std::atomic<bool> flag(false);          // 原子布尔(标志位)
std::atomic<int> counter(0);            // 原子整型(计数器)
std::atomic<long long> big_counter(0);  // 原子长整型
std::atomic<int*> ptr(nullptr);         // 原子指针

// 2. 自定义平凡类型(需 C++20 或编译器支持,部分编译器 C++11 即可)
struct Point {
    int x;
    int y;
    // 必须是平凡复制类型(无自定义拷贝构造/赋值运算符、析构函数)
};
std::atomic<Point> atomic_point{{1, 2}};  // 原子结构体
关键注意:
  • 原子变量不能被拷贝或赋值(拷贝构造和赋值运算符被禁用),只能通过原子操作修改;
  • 若自定义类型非平凡复制(如含 std::string、指针),使用 std::atomic<T> 会编译失败(需用 std::atomic_ref 或锁保护)。

2. 核心原子操作

std::atomic 提供了两类操作接口:成员函数(更灵活)和全局函数(兼容 C 风格),核心操作如下:

(1)读 / 写操作(load/store)
  • load(memory_order = std::memory_order_seq_cst):原子读取变量值,返回类型为 T
  • store(T val, memory_order = std::memory_order_seq_cst):原子写入变量值,无返回;
  • 内存序(memory_order)默认是 seq_cst(顺序一致),后续会详细解释。
void write_flag() {
    flag.store(true);  // 原子写入 true
}

void read_flag() {
    bool val = flag.load();  // 原子读取 flag 值
    if (val) {
        std::cout << "Flag is true\n";
    }
}

int main() {
    std::thread t1(write_flag);
    std::thread t2(read_flag);
    t1.join();
    t2.join();
    return 0;
}
(2)自增 / 自减操作(针对数值类型)

std::atomic<int>std::atomic<long> 等数值类型支持原子自增 / 自减,无需手动实现 CAS:

成员函数 功能 等价操作(非原子)
fetch_add(T val) 原子 += val,返回旧值 old = x; x += val
fetch_sub(T val) 原子 -= val,返回旧值 old = x; x -= val
operator++() 前缀自增(返回新值) ++x
operator++(int) 后缀自增(返回旧值) x++
operator--() 前缀自减(返回新值) --x
operator--(int) 后缀自减(返回旧值) x--

示例:原子计数器(多线程安全累加)

const int THREAD_NUM = 10;
std::atomic<int> counter(0);

void increment() {
    for (int i = 0; i < 10000; ++i) {
        counter.fetch_add(1);  // 原子自增,返回旧值(此处无需旧值)
        // 等价写法:counter++ 或 ++counter
    }
}

int main() {
    std::thread threads[THREAD_NUM];
    for (int i = 0; i < THREAD_NUM; ++i) {
        threads[i] = std::thread(increment);
    }
    for (auto& t : threads) {
        t.join();
    }
    std::cout << "Final counter: " << counter << std::endl;  // 必然输出 100000
    return 0;
}

  • 若用普通 int counter,多线程累加会出现数据竞争,最终结果小于 100000;
  • std::atomic<int> 保证每次 fetch_add 是原子操作,无需加锁即可安全并发。
(3)CAS 操作(compare_exchange_weak/strong)

CAS(Compare-And-Swap)是原子操作的核心,std::atomic 提供 compare_exchange_weak 和 compare_exchange_strong 两个成员函数,功能是:

原子性比较当前值与 expected(预期值):

 
  • 若相等:将当前值更新为 desired(目标值),返回 true
  • 若不相等:将 expected 更新为当前值,返回 false
函数签名:

cpp

// 弱版本:可能出现“伪失败”(当前值 == expected 但返回 false,需循环重试)
bool compare_exchange_weak(T& expected, T desired, 
                           memory_order success, memory_order failure);
bool compare_exchange_weak(T& expected, T desired,
                           memory_order order = std::memory_order_seq_cst);

// 强版本:无伪失败,当前值 == expected 时必返回 true(性能略低于弱版本)
bool compare_exchange_strong(T& expected, T desired,
                             memory_order success, memory_order failure);
bool compare_exchange_strong(T& expected, T desired,
                             memory_order order = std::memory_order_seq_cst);
示例:用 CAS 实现自定义原子操作(如原子最大值更新)

cpp

std::atomic<int> max_val(0);

// 多线程尝试更新 max_val 为当前值与 new_val 中的较大者
void update_max(int new_val) {
    int expected = max_val.load();  // 读取当前预期值
    // 弱版本需循环:处理伪失败和并发修改
    while (new_val > expected && !max_val.compare_exchange_weak(expected, new_val)) {
        // 若失败,expected 已被更新为当前 max_val,重新进入循环比较
    }
}

int main() {
    std::thread t1(update_max, 10);
    std::thread t2(update_max, 20);
    std::thread t3(update_max, 15);
    t1.join();
    t2.join();
    t3.join();
    std::cout << "Max value: " << max_val << std::endl;  // 输出 20
    return 0;
}
弱版本 vs 强版本选择:
  • 弱版本:适合 “循环重试” 场景(如上述示例),性能更高(尤其在 ARM、Power 等弱内存模型 CPU 上);
  • 强版本:适合 “无需重试” 的场景(如单次尝试更新),避免伪失败导致的逻辑错误。

3. 内存序(memory_order):灵活控制并发顺序

std::atomic 的所有原子操作都支持可选的 memory_order 参数,用于控制操作的内存可见性和指令重排序规则。默认的 std::memory_order_seq_cst(顺序一致)是最严格的,能保证所有线程看到的原子操作顺序完全一致,但性能开销较高。

实际开发中可根据需求选择更宽松的内存序,平衡性能和正确性:

内存序类型 核心语义 适用场景
memory_order_seq_cst 顺序一致:所有原子操作按全局统一顺序执行(默认) 需严格顺序的场景(如全局计数器)
memory_order_acquire 读操作:禁止后续指令重排序到该操作之前;保证该操作读取的值是最新的 读取共享资源后,后续操作依赖该值
memory_order_release 写操作:禁止之前的指令重排序到该操作之后;保证该操作的修改对其他线程可见 写入共享资源后,之前的修改需同步
memory_order_acq_rel 读写操作:同时具备 acquire 和 release 语义(如 CAS 成功时) 读写一体的操作(如 fetch_add
memory_order_relaxed 松散:仅保证操作本身的原子性,不保证可见性和有序性 无依赖的独立操作(如统计次数)
示例:用 acquire/release 优化性能(生产者 - 消费者模型)

cpp

std::atomic<bool> ready(false);  // 生产者是否完成
std::atomic<int> data(0);        // 共享数据

// 生产者线程:写入数据后,标记 ready 为 true
void producer() {
    data.store(100, std::memory_order_relaxed);  // 数据写入无需顺序约束
    ready.store(true, std::memory_order_release); // release:data 的修改对消费者可见
}

// 消费者线程:等待 ready 为 true 后读取数据
void consumer() {
    // acquire:读取 ready 为 true 时,确保 data 的修改已可见
    while (!ready.load(std::memory_order_acquire)) {
        // 自旋等待
    }
    std::cout << "Data: " << data.load(std::memory_order_relaxed) << std::endl;  // 输出 100
}

int main() {
    std::thread t_prod(producer);
    std::thread t_cons(consumer);
    t_prod.join();
    t_cons.join();
    return 0;
}

  • 生产者的 ready.store(release) 保证:data.store 一定在 ready.store 之前执行,且 data 的修改对消费者可见;
  • 消费者的 ready.load(acquire) 保证:data.load 一定在 ready.load 之后执行,且能读取到生产者写入的 data
  • 相比默认的 seq_cstacquire/release 更宽松,性能更高。

三、std::atomic 的关键特性与限制

1. 核心特性

  • 无锁性(Lock-Free):大多数 std::atomic 特化版本(如 boolint、指针)是 “无锁” 的,可通过 is_lock_free() 成员函数判断:

    cpp

    std::atomic<int> a;
    std::cout << "Is lock-free? " << (a.is_lock_free() ? "Yes" : "No") << std::endl;  // 通常输出 Yes
    

    若类型过大(如自定义结构体超过 CPU 原子操作支持的宽度),std::atomic 可能退化为 “有锁实现”(底层用互斥锁),此时 is_lock_free() 返回 false
  • 不可拷贝 / 赋值:原子变量的拷贝构造、赋值运算符被删除,只能通过 load()/store() 或原子操作传递值:

    cpp

    std::atomic<int> a(10);
    // std::atomic<int> b = a;  // 编译失败:拷贝构造禁用
    int b = a.load();  // 正确:通过 load 读取值
    
  • 支持指针类型std::atomic<T*> 可实现原子指针操作(如原子修改指针指向、原子 fetch_add 实现数组元素访问):

    cpp

    int arr[5] = {1,2,3,4,5};
    std::atomic<int*> p(arr);
    p.fetch_add(1);  // 原子指针自增,指向 arr[1]
    std::cout << *p.load() << std::endl;  // 输出 2
    

2. 常见限制

  • 不支持复杂操作的原子性std::atomic 仅保证 “单个操作” 的原子性,若需多个原子操作组成 “原子序列”(如 “读取 - 修改 - 写入” 多步),需手动用 CAS 循环或锁保护:

    cpp

    // 错误:两个原子操作,整体非原子(可能被其他线程打断)
    if (counter.load() < 10) {
        counter.fetch_add(1);
    }
    // 正确:用 CAS 循环保证原子性
    int expected = counter.load();
    while (expected < 10 && !counter.compare_exchange_weak(expected, expected + 1)) {}
    
  • 自定义类型需平凡复制:若自定义类型 T 非平凡复制(如含 std::vector、自定义析构函数),std::atomic<T> 会编译失败(C++20 可通过 std::atomic_ref 解决);
  • 内存序使用需谨慎:宽松内存序(如 relaxed)若使用不当,会导致数据可见性问题,需明确操作间的依赖关系。

四、std::atomic vs 锁(std::mutex):选择场景

对比维度 std::atomic std::mutex(互斥锁)
适用场景 单个变量的简单同步(计数器、标志位、指针) 复杂临界区(多个变量、多步操作)
性能开销 低(无锁,依赖 CPU 原子指令) 高(上下文切换、锁竞争开销)
原子性范围 单个操作(load/store、fetch_add、CAS 等) 整个临界区(多个操作组成的序列)
易用性 简单(接口直观,无需手动加解锁) 复杂(需手动管理 lock/unlock,避免死锁)
灵活性 低(仅支持单个变量操作) 高(支持任意临界区逻辑)

选择建议:

  • 若只需同步 “单个变量”(如计数器、开关标志),优先用 std::atomic(无锁、高效);
  • 若需同步 “多个变量” 或 “多步操作”(如修改结构体的多个字段、读取后写入新值并更新其他变量),用 std::mutex 或 std::unique_lock(保证临界区原子性)。

五、实战场景:std::atomic 的典型应用

1. 原子计数器(多线程统计)

cpp

#include <atomic>
#include <thread>
#include <vector>
#include <iostream>

std::atomic<size_t> request_count(0);

void handle_requests() {
    for (int i = 0; i < 100000; ++i) {
        ++request_count;  // 原子自增,统计请求数
    }
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 8; ++i) {
        threads.emplace_back(handle_requests);
    }
    for (auto& t : threads) {
        t.join();
    }
    std::cout << "Total requests: " << request_count << std::endl;  // 800000
    return 0;
}

2. 线程安全的单例模式(无锁)

cpp

#include <atomic>
#include <memory>

class Singleton {
public:
    static Singleton& get_instance() {
        // 第一次检查:无锁,快速判断是否已初始化(relaxed 足够,仅需原子性)
        Singleton* ptr = instance.load(std::memory_order_relaxed);
        if (!ptr) {
            // 第二次检查:加锁,避免多线程同时初始化
            std::lock_guard<std::mutex> lock(mtx);
            ptr = instance.load(std::memory_order_relaxed);
            if (!ptr) {
                ptr = new Singleton();
                // release:确保 Singleton 构造完成后,才更新 instance
                instance.store(ptr, std::memory_order_release);
            }
        }
        // acquire:确保 instance 指向的对象已完全构造
        return *instance.load(std::memory_order_acquire);
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
    static std::atomic<Singleton*> instance;
    static std::mutex mtx;
};

std::atomic<Singleton*> Singleton::instance(nullptr);
std::mutex Singleton::mtx;

  • 用 std::atomic<Singleton*> 原子存储单例指针,避免 “双重检查锁定”(DCLP)的经典问题;
  • 结合 acquire/release 内存序,保证单例对象构造完成后才对其他线程可见。

3. 无锁队列的核心(基于 CAS)

std::atomic 是无锁数据结构(如无锁队列、无锁栈)的基础,核心是用 CAS 操作原子修改队列的头 / 尾指针:

cpp

template <typename T>
class LockFreeQueue {
private:
    struct Node {
        T data;
        std::atomic<Node*> next;
        Node(T val) : data(val), next(nullptr) {}
    };

    std::atomic<Node*> head;
    std::atomic<Node*> tail;

public:
    LockFreeQueue() : head(new Node(T())), tail(head.load()) {}

    void push(T val) {
        Node* new_node = new Node(val);
        Node* old_tail = tail.load(std::memory_order_relaxed);
        // CAS 更新 tail->next 为新节点
        while (!old_tail->next.compare_exchange_weak(
            nullptr, new_node,
            std::memory_order_release,
            std::memory_order_relaxed)) {
            old_tail = tail.load(std::memory_order_relaxed);
        }
        // 更新 tail 指针
        tail.compare_exchange_strong(old_tail, new_node, std::memory_order_release);
    }

    // 省略 pop 实现(类似 push,需原子修改 head 指针)
};
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐