news 2026/6/24 19:13:07

C++原子变量(std::atomic)实战:从基础到高效多线程编程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++原子变量(std::atomic)实战:从基础到高效多线程编程

1. 为什么需要原子变量?

想象一下你和室友共用一个冰箱,当你们同时打开冰箱门拿牛奶时,可能会发生"牛奶争夺战"。在多线程编程中,std::atomic就是那个帮你避免资源争夺的智能管家。它能让多个线程安全地访问共享数据,而不会出现数据错乱的情况。

传统方式中,我们会用互斥锁(mutex)来保护共享数据,就像给冰箱加把锁。但锁的开销很大,每次拿牛奶都要开锁关锁。而原子操作就像冰箱的智能感应门,不需要显式加锁就能安全存取物品。

我在一个高频交易系统中就遇到过这个问题。最初使用mutex保护计数器,性能测试时发现锁竞争导致吞吐量下降40%。改用atomic后,不仅解决了线程安全问题,性能还提升了35%。

2. 原子变量基础操作

2.1 创建原子变量

创建atomic变量就像声明普通变量一样简单,只是多了一层"原子包装":

#include <atomic> std::atomic<int> counter(0); // 原子整型,初始值为0 std::atomic<bool> ready(false); // 原子布尔值 std::atomic<double> price(99.99); // 原子浮点数(注意有坑!)

这里有个实际项目中的经验:不是所有类型都适合做原子操作。比如浮点数的atomic在某些平台可能不是无锁的。我曾经在一个跨平台项目中踩过这个坑,后来改用整型才解决问题。

2.2 基本读写操作

原子变量的读写有专门的方法:

// 写入值 counter.store(42); // 相当于 counter = 42,但是线程安全的 // 读取值 int current = counter.load(); // 相当于 int current = counter // 复合操作 int old = counter.exchange(100); // 把值设为100,返回旧值

在开发消息队列时,我发现exchange特别有用。可以用它来实现一个简单的自旋锁:

std::atomic<bool> lock(false); // 获取锁 while(lock.exchange(true)) {} // 忙等待 // 临界区操作... // 释放锁 lock.store(false);

3. 原子操作进阶技巧

3.1 原子算术运算

atomic提供了一系列原子算术运算,这些是真正的"杀手锏":

std::atomic<int> value(10); // 原子加法 int prev = value.fetch_add(5); // value +=5,返回旧值 // 原子减法 prev = value.fetch_sub(3); // value -=3 // 原子递增/递减 value++; // 等价于fetch_add(1)

在实现无锁队列时,这些操作特别关键。我曾经用fetch_add实现了一个多线程安全的环形缓冲区索引:

std::atomic<size_t> write_index(0); // 多线程安全获取写入位置 size_t get_write_position() { return write_index.fetch_add(1) % buffer_size; }

3.2 比较交换(CAS)操作

compare_exchange是原子操作中最强大的武器,它是很多无锁算法的基础:

std::atomic<int> value(100); int expected = 100; bool success = value.compare_exchange_strong(expected, 200); // 如果value==100,就设为200,返回true // 否则把expected设为当前值,返回false

我在实现线程安全的LRU缓存时,就大量使用了CAS操作。比如更新缓存命中计数:

void update_hit_count() { int old = hit_count.load(); int new_val; do { new_val = old + 1; } while(!hit_count.compare_exchange_weak(old, new_val)); }

这里用了compare_exchange_weak配合循环,比直接用compare_exchange_strong性能更好,特别是在高竞争场景下。

4. 性能优化与陷阱规避

4.1 内存顺序选择

atomic操作可以指定内存顺序,这对性能影响很大:

// 最宽松的顺序,性能最好但同步保证最少 counter.fetch_add(1, std::memory_order_relaxed); // 常用的是acquire-release语义 data.store(new_value, std::memory_order_release); int value = data.load(std::memory_order_acquire);

在开发一个高性能网络服务器时,经过测试发现:

  • 使用memory_order_seq_cst(默认)时,QPS是12万
  • 优化为合适的memory_order后,QPS提升到18万

4.2 常见陷阱

  1. 误以为所有操作都是原子的
std::atomic<int> x(1); x = x * 2; // 不是原子操作!

正确做法是使用fetch_mul(如果支持)或CAS。

  1. ABA问题在CAS操作中,值从A变B又变回A,可能导致错误判断。解决方案是使用带版本号的指针。

  2. 虚假失败compare_exchange_weak可能在硬件层面失败,即使值符合预期。所以要用循环重试。

我在实现无锁栈时就遇到过ABA问题,后来改用带计数器的指针才解决:

struct Node { void* data; Node* next; }; struct CountedPtr { Node* ptr; uintptr_t count; }; std::atomic<CountedPtr> top;

5. 实际应用案例

5.1 高性能计数器

这是atomic最直接的用途:

class ThreadSafeCounter { public: void increment() { count.fetch_add(1); } int get() const { return count.load(); } private: std::atomic<int> count{0}; };

在日志系统中,我们用这种计数器统计各等级的日志数量,性能比mutex版本高3倍。

5.2 无锁队列

更复杂的例子是MPSC(多生产者单消费者)队列:

template<typename T> class LockFreeQueue { public: void push(const T& value) { Node* new_node = new Node{value}; Node* old_tail = tail.load(); while(!tail.compare_exchange_weak(old_tail, new_node)) {} old_tail->next = new_node; } bool pop(T& value) { Node* old_head = head.load(); if(old_head == tail.load()) return false; value = old_head->next->data; head.store(old_head->next); delete old_head; return true; } private: struct Node { T data; Node* next; }; std::atomic<Node*> head, tail; };

5.3 双重检查锁定

单例模式的经典实现:

class Singleton { public: static Singleton* getInstance() { Singleton* tmp = instance.load(std::memory_order_acquire); if(!tmp) { std::lock_guard<std::mutex> lock(mutex); tmp = instance.load(std::memory_order_relaxed); if(!tmp) { tmp = new Singleton(); instance.store(tmp, std::memory_order_release); } } return tmp; } private: static std::atomic<Singleton*> instance; static std::mutex mutex; };

在框架开发中,这种模式可以避免不必要的锁竞争,提升性能。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/13 13:19:25

油菜排种器(论文+CAD+SolidWorks+step+x_t)

在农业机械领域&#xff0c;油菜排种器作为播种环节的核心部件&#xff0c;直接影响着播种效率与作物产量。传统排种方式多依赖人工或简单机械结构&#xff0c;存在播种不均匀、漏播率高、种子损伤等问题&#xff0c;难以满足现代农业对精准作业的需求。而经过优化设计的油菜排…

作者头像 李华
网站建设 2026/6/11 16:54:39

Rustup终极指南:如何高效管理Rust多版本开发环境

Rustup终极指南&#xff1a;如何高效管理Rust多版本开发环境 【免费下载链接】rustup The Rust toolchain installer 项目地址: https://gitcode.com/gh_mirrors/ru/rustup Rustup是Rust编程语言的官方工具链管理器&#xff0c;它解决了开发者在实际工作中遇到的多版本管…

作者头像 李华
网站建设 2026/4/29 18:05:05

ADB Explorer:颠覆性Android文件管理体验,告别繁琐命令行

ADB Explorer&#xff1a;颠覆性Android文件管理体验&#xff0c;告别繁琐命令行 【免费下载链接】ADB-Explorer A fluent UI for ADB on Windows 项目地址: https://gitcode.com/gh_mirrors/ad/ADB-Explorer 你是否曾经为Android设备的文件管理而烦恼&#xff1f;想象一…

作者头像 李华
网站建设 2026/4/13 13:17:20

Obsidian插件翻译终极指南:3步实现英文插件完美汉化

Obsidian插件翻译终极指南&#xff1a;3步实现英文插件完美汉化 【免费下载链接】obsidian-i18n 项目地址: https://gitcode.com/gh_mirrors/ob/obsidian-i18n 还在为Obsidian插件全是英文界面而烦恼吗&#xff1f;想要快速将心爱的插件变成中文版&#xff0c;提升工作…

作者头像 李华
网站建设 2026/4/13 13:16:14

embeddinggemma-300m效果展示:新闻标题跨语言语义相似度匹配实例

embeddinggemma-300m效果展示&#xff1a;新闻标题跨语言语义相似度匹配实例 1. 引言&#xff1a;当AI能理解不同语言的新闻时 想象一下这个场景&#xff1a;你是一位市场研究员&#xff0c;需要追踪全球社交媒体上关于某个产品的讨论。你可能会看到英文的“New smartphone l…

作者头像 李华
网站建设 2026/6/18 0:01:29

lec1:算法分析——以插入排序和归并排序为例

lec1-算法导论算法分析&#xff1a;插入排序&#xff0c;渐近分析&#xff0c;归并排序&#xff0c;递归式【算法】&#xff1a;定义明确的计算过程&#xff0c;接收一个或一组值作为输入&#xff0c;经过一系列计算步骤将输入转换为一个或一组值作为输出。 简单来说&#xff0…

作者头像 李华