C++多线程中如何利用原子变量的fetch_add实现高效计数

秋婷吖_8798

秋婷吖_8798

2026-09-20

307人浏览

原创

fetch_add比加锁计数快,因其在单条cpu指令中完成读-改-写,避免上下文切换与内核态开销,缓存命中时仅需几十周期,而锁操作通常需数百周期以上。

c++多线程中如何利用原子变量的fetch_add实现高效计数

fetch_add 为什么比加锁计数快 fetch_addstd::atomic 提供的原子读-改-写操作,它在单条 CPU 指令(如 x86 的 xadd 或 ARM 的 ldadd)中完成“读取当前值 → 加上指定增量 → 写回 → 返回旧值”全过程。这避免了互斥锁(std::mutex)带来的上下文切换、内核态陷入和排队等待开销。 关键点在于:只要目标变量是缓存在 CPU 核心的 L1 cache 中,fetch_add 通常只需几十个周期;而一次轻量锁的 lock/unlock 在争用不激烈时也要数百周期,争用高时可能飙升到微秒级。

注意:不是所有类型都支持 fetch_add —— 仅限整型(int, long, size_t 等)和指针类型;浮点型需用 fetch_add 的特化版本(C++20 起)或退回到 compare_exchange_weak 循环。

正确声明和初始化 atomic 计数器 必须显式指定内存序(memory order),否则默认为 std::memory_order_seq_cst,安全但略重。对纯计数场景,通常可用更宽松的序:

常见初始化方式:

std::atomic<int> counter{0};                    // OK,值初始化为 0
std::atomic<long> total_bytes{0L};           // 注意字面量类型匹配
std::atomic<size_t> request_count{};        // 值初始化,等价于 = 0</size_t></long></int>

容易踩的坑:

  • 忘记初始化:未初始化的 std::atomic<int></int> 对象值是未定义的,不是 0
  • = 0 初始化非 trivial 类型(如自定义 struct)会编译失败;只对基础整型/指针安全
  • 跨线程访问前未确保对象生命周期——比如在主线程构造后,子线程才开始读写,但对象若在子线程启动前就析构了,行为未定义

fetch_add 的参数和返回值怎么用 fetch_add 接收一个增量参数(可正可负),返回操作前的旧值。典型用法是“先取旧值,再基于它做逻辑判断”。

例如实现带阈值的计数上报:

C++
C++

"空空如也"

下载
if (counter.fetch_add(1, std::memory_order_relaxed) == 99) {
    report_to_server(counter.load(std::memory_order_relaxed));
}

这里用 std::memory_order_relaxed 是因为计数本身不要求与其他内存操作同步,只关心原子性。但要注意:

  • 返回的是“加之前的值”,所以 == 99 表示本次加 1 后变成 100
  • 多个线程可能同时看到 fetch_add 返回 99(竞态),导致多次上报;若需严格一次,得配合 compare_exchange 或额外锁
  • std::memory_order_relaxed 不禁止编译器/CPU 重排,若该计数和其他变量(如标志位)有逻辑依赖,必须升级内存序(如 acq_rel

性能差异在哪?什么时候不该用 fetch_add 在低争用(线程数 ≤ CPU 核心数)且操作简单(仅增减)时,fetch_add 比锁快 3–10 倍;但在高争用下,缓存行频繁在核心间传递(false sharing),反而可能比锁慢。

需要警惕的场景:

  • 多个原子变量紧挨着定义(如 std::atomic<int> a, b, c;</int>),可能落在同一缓存行(64 字节),造成 false sharing —— 解决方法是用 alignas(64) 隔离
  • 需要复合操作:比如“如果小于阈值则加 1,否则跳过”,这时 fetch_add 不够用,得用 compare_exchange_weak 循环
  • 计数结果要立刻用于控制流(如 if 分支),且分支结果影响后续内存访问,则需更强的内存序保证,不能无脑用 relaxed

真正难的不是写对 fetch_add,而是判断它是否适合你的数据流语义和并发模型——尤其当计数嵌套在更大状态机里时,内存序和可见性边界很容易被忽略。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关专题

更多
c++和c语言的区别有哪些
c++和c语言的区别有哪些

c++和c语言的区别:1、面向对象编程(OOP)支持不同;2、新增特性不同;3、标准库不同;4、编译方式不同;5、命名空间不同等等。想了解更多c++的相关内容,可以阅读本专题下面的文章。

2024.03.14

2068

9

c++和python学习顺序推荐
c++和python学习顺序推荐

一般建议先学习C++,再学习Python,因为这样可以逐步从较为底层的编程语言向更高级的语言过渡。想了解更多python的相关内容,可以阅读本专题下面的文章。

2024.03.14

939

6

python和c++学习性价比分析
python和c++学习性价比分析

Python易于学习,广泛应用于Web开发、数据科学和人工智能等领域,但性能较低。C语言性能高,适用于对性能要求较高的场景,如游戏开发和系统编程,但学习曲线陡峭,错误处理复杂。想了解更多python的相关内容,可以阅读本专题下面的文章。

2024.03.14

367

5

c语言和c++一样吗
c语言和c++一样吗

c语言和c++是两种不同的编程语言,虽然有相似之处,但存在显著差异。c语言专注于过程式编程和系统级开发,以简洁、高效著称。c++作为c语言的超集,引入了面向对象编程,增强了代码组织和管理能力,但学习曲线也更陡峭。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.14

307

5

c语言和c++先学哪个好
c语言和c++先学哪个好

初学者选择学习c语言还是c++语言,需要根据个人学习目标、背景以及编程兴趣和预期应用方向来决定。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.14

346

5

c语言和c++的区别和联系
c语言和c++的区别和联系

c语言和c++是计算机科学领域应用广泛的编程语言。虽然它们有着相似的基础,但它们在语言类型、语法功能和内存管理方面存在着显著差异。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.14

560

5

c++软件中文更改教程
c++软件中文更改教程

对于 ide,可通过打开设置,找到语言设置,选择中文,并保存更改。对于非 ide 应用程序,可查找设置或选项,选择语言设置,更改为中文,并保存更改。想了解更多c++的相关内容,可以阅读本专题下面的文章。

2024.03.21

1389

9

python和java和c++学习性价比分析
python和java和c++学习性价比分析

Python以其易学性、丰富的库和活跃的社区而著称,适合数据科学、人工智能和Web开发。Java以其跨平台性、企业级应用开发和Android应用开发而闻名。C++以其底层控制能力、高效性能和游戏开发而著称。选择哪种语言取决于个人兴趣、职业方向和特定需求。想了解更多python和java和c++的相关内容,可以阅读本专题下面的文章。

2024.03.22

1177

7

c++和c语言学习顺序推荐
c++和c语言学习顺序推荐

对于初学者,建议先学习C语言,掌握编程基础后再转入C++,便于理解面向对象编程概念。有编程经验者可直接学习C++,快速接触高级编程技术。想了解更多c++和c语言的相关内容,可以阅读本专题下面的文章。

2024.03.25

1305

9

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Valgrind Quick Start Guide
Valgrind Quick Start Guide

共0课时 | 0人学习

CLion CMake 快速入门教程
CLion CMake 快速入门教程

共0课时 | 0人学习