C++如何使用std::atomic保证原子操作

来源:编程网作者:多肉头衔:草根站长
导读:本期聚焦于小伙伴创作的《C++如何使用std::atomic保证原子操作》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C++如何使用std::atomic保证原子操作》有用,将其分享出去将是对创作者最好的鼓励。

在C++多线程编程场景中,多个线程同时读写同一个共享变量时,很容易出现数据竞争问题,比如两个线程同时对一个计数器做自增操作,最终得到的结果可能小于预期值。std::atomic是C++11标准引入的原子类型模板,能够对指定类型的变量执行原子操作,从底层保证操作的不可分割性,从根源上避免数据竞争问题。

std::atomic的基本使用方式

std::atomic是一个模板类,支持大部分内置数据类型,比如int、long、bool、指针类型等,部分自定义类型如果满足特定条件也可以使用。使用std::atomic需要先包含<atomic>头文件,声明原子变量的方式和普通变量类似,只是类型需要包裹在std::atomic模板中。

下面是一个简单的计数器示例,展示普通变量和原子变量在多线程场景下的差异:

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

// 普通计数器
int normal_counter = 0;
// 原子计数器
std::atomic<int> atomic_counter(0);

// 线程执行的函数,对计数器做自增操作
void increment(int times) {
    for (int i = 0; i < times; ++i) {
        // 普通变量自增,非原子操作
        normal_counter++;
        // 原子变量自增,原子操作
        atomic_counter++;
    }
}

int main() {
    const int thread_num = 4;
    const int increment_times = 10000;
    std::vector<std::thread> threads;

    // 创建4个线程同时执行自增操作
    for (int i = 0; i < thread_num; ++i) {
        threads.emplace_back(increment, increment_times);
    }

    // 等待所有线程执行完成
    for (auto& t : threads) {
        t.join();
    }

    std::cout << "普通计数器最终值: " << normal_counter << std::endl;
    std::cout << "原子计数器最终值: " << atomic_counter.load() << std::endl;
    return 0;
}

上述代码中,4个线程每个对计数器做10000次自增,理论上最终结果应该是40000。普通计数器因为自增操作不是原子的,最终结果通常会小于40000,而原子计数器的最终结果一定是40000,这就是std::atomic保证原子操作的效果。

std::atomic的常用操作

std::atomic提供了多种原子操作接口,常用的有以下几种:

  • load():原子读取当前原子变量的值,默认使用内存顺序std::memory_order_seq_cst
  • store(val):原子将值val写入原子变量
  • fetch_add(val):原子自增,返回自增前的值
  • fetch_sub(val):原子自减,返回自减前的值
  • operator++/--:重载的自增自减运算符,执行原子自增自减操作
  • compare_exchange_strong(expected, desired):比较交换操作,如果当前值等于expected,就原子替换为desired,返回是否替换成功

下面是一个使用compare_exchange_strong实现简单自旋锁的示例:

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

// 自旋锁,0表示未锁定,1表示锁定
std::atomic<int> spin_lock(0);

// 加锁操作
void lock() {
    int expected = 0;
    // 循环尝试将锁从0改为1,直到成功
    while (!spin_lock.compare_exchange_strong(expected, 1)) {
        expected = 0; // 失败后重置expected,继续尝试
    }
}

// 解锁操作
void unlock() {
    spin_lock.store(0);
}

int shared_data = 0;

void thread_func(int id) {
    lock();
    // 临界区操作,修改共享数据
    shared_data++;
    std::cout << "线程" << id << "修改共享数据,当前值: " << shared_data << std::endl;
    unlock();
}

int main() {
    std::thread t1(thread_func, 1);
    std::thread t2(thread_func, 2);
    t1.join();
    t2.join();
    return 0;
}

原子操作的内存顺序

std::atomic的所有操作都支持指定内存顺序,内存顺序决定了原子操作之间的可见性和重排序规则,常用的内存顺序有以下几种:

内存顺序说明
std::memory_order_relaxed宽松内存顺序,只保证操作是原子的,不保证操作的顺序和可见性,性能最高
std::memory_order_acquire获取操作,当前线程后续的读写操作不能被重排序到该操作之前,且能看到其他线程释放操作之前的所有写入
std::memory_order_release释放操作,当前线程之前的读写操作不能被重排序到该操作之后,且之前的写入对其他线程的获取操作可见
std::memory_order_seq_cst顺序一致内存顺序,默认使用的顺序,保证所有原子操作的全局顺序一致,性能相对较低但最安全

如果不需要特殊的内存顺序约束,直接使用默认的std::memory_order_seq_cst即可,大部分场景下都能满足需求。如果对性能要求极高,且明确知道内存顺序的约束,可以选择更宽松的内存顺序提升性能。

std::atomic实现多线程同步的常见方法

除了直接保证单个变量的原子操作,std::atomic还可以配合内存顺序实现多线程之间的同步,常见的场景有以下几种:

1. 标志位同步

使用一个原子布尔变量作为标志位,一个线程修改标志位,其他线程等待标志位变化,实现线程之间的通知。

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

std::atomic<bool> ready(false);

void worker_thread() {
    // 等待主线程设置ready为true
    while (!ready.load(std::memory_order_acquire)) {
        // 空循环等待
    }
    std::cout << "工作线程开始执行任务" << std::endl;
}

int main() {
    std::thread worker(worker_thread);
    // 主线程做一些准备工作
    std::cout << "主线程准备完成,通知工作线程" << std::endl;
    ready.store(true, std::memory_order_release);
    worker.join();
    return 0;
}

2. 计数器同步

使用原子计数器统计完成的线程数量,当计数器达到指定值时,通知等待的线程继续执行,类似线程屏障的效果。

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

std::atomic<int> finished_count(0);
const int total_threads = 3;

void task_thread(int id) {
    std::cout << "线程" << id << "执行任务完成" << std::endl;
    finished_count.fetch_add(1, std::memory_order_acq_rel);
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < total_threads; ++i) {
        threads.emplace_back(task_thread, i);
    }
    // 等待所有线程完成任务
    while (finished_count.load(std::memory_order_acquire) < total_threads) {
        // 空循环等待
    }
    std::cout << "所有线程任务完成,主线程继续执行" << std::endl;
    for (auto& t : threads) {
        t.join();
    }
    return 0;
}

使用std::atomic的注意事项

使用std::atomic时需要注意以下几点:

  • 不是所有类型都可以作为std::atomic的模板参数,只有满足可平凡复制(TriviallyCopyable)的类型才能使用,否则编译会报错
  • 原子操作的开销比普通操作要高,因为需要保证操作的原子性和内存顺序,不要对所有变量都使用std::atomic,只对多线程共享的变量使用
  • std::atomic的默认构造函数不会初始化变量,需要显式初始化,避免未定义行为
  • 对于需要同时保证多个变量原子性的场景,std::atomic无法单独满足,需要结合互斥锁等其他同步机制使用

总的来说,std::atomic是C++中实现轻量级原子操作和线程同步的重要工具,在只需要保证单个共享变量线程安全的场景下,比互斥锁的性能更好,是多线程开发中的常用组件。

std::atomicC++原子类型多线程同步原子操作线程安全修改时间:2026-06-28 00:03:45

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。