好的,我们用 教科书级别 的方式来讲解 std::atomic<int>
以及你提到的数组用法,并给出完整案例。
1️⃣ 基础概念:原子(Atomic)
- 原子操作:操作要么全部完成,要么完全不发生,中间不会被线程切换打断。
- 在多线程环境下,如果两个线程同时写同一个普通变量,会产生 数据竞争(Data Race)。
std::atomic<T>
就是 C++ 提供的标准库原子类型,用来保证单个变量的操作在多线程下线程安全。
关键特点:
- 不需要手动加锁
mutex
。 - 对于基本类型(如 int、bool、指针)性能更高。
- 只保证单个变量原子性,不能保证多个变量一起原子更新(那就需要
mutex
或更高级的原子结构)。
2️⃣ std::atomic<int>
数组
std::atomic<int> cpu_use_rate[8];
含义:
- 创建一个长度为 8 的原子整数数组。
- 每个元素都是线程安全的,可以单独读写而不会产生数据竞争。
- 不同元素之间访问互不干扰(每个元素独立原子)。
3️⃣ 常用操作
假设:
std::atomic<int> cpu_use_rate[8];
写入(store)
cpu_use_rate[0].store(50); // 将第0个CPU占用率设为50%
读取(load)
int usage = cpu_use_rate[0].load(); // 获取第0个CPU占用率
更方便的操作符
cpu_use_rate[0] = 60; // 等价于 store
int u = cpu_use_rate[0]; // 等价于 load
原子自增/自减
cpu_use_rate[1]++; // 原子加1
cpu_use_rate[2]--; // 原子减1
4️⃣ 教科书级案例
场景:多线程更新 CPU 占用率
#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
std::atomic<int> cpu_use_rate[8]; // 8核CPU占用率
void simulateCpuLoad(int core, int times) {
for (int i = 0; i < times; i++) {
cpu_use_rate[core] = i * 10; // 原子写
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void printCpuLoad() {
for (int i = 0; i < 8; i++) {
std::cout << "CPU" << i << ": " << cpu_use_rate[i] << "%\n";
}
}
int main() {
// 初始化
for (int i = 0; i < 8; i++) cpu_use_rate[i] = 0;
// 启动8个线程分别模拟CPU占用变化
std::vector<std::thread> threads;
for (int i = 0; i < 8; i++) {
threads.emplace_back(simulateCpuLoad, i, 5);
}
// 等待线程完成
for (auto& t : threads) t.join();
// 打印最终CPU占用
printCpuLoad();
return 0;
}
说明:
cpu_use_rate
是原子数组,每个线程独立修改自己的核占用率。- 没有
mutex
,不会产生数据竞争。 - 性能比加锁高,尤其适合单个整型变量频繁读写的场景。
5️⃣ 小结
std::atomic<int>
= 单变量的原子操作,读写安全,无需加锁。- 对数组中的每个元素,操作独立原子。
- 优点:性能高、简单。
- 局限:无法保证多个变量一起的原子性,需要
mutex
或其他同步机制。
如果你愿意,我可以帮你把你之前 Device_Info::devInfo.cpu_use_rate[8]
的 get/set 全部改成 原子数组实现版,这样就 读写无需锁,性能会更好。
你希望我直接帮你改吗?
完全可以给你讲清楚,这里涉及 C++ 原子类型 vs C 普通变量多线程访问 的核心区别,以及“线程中断”和“中断打断”的问题。我们逐条分析。
1️⃣ 为什么 C++ std::atomic
不需要加锁
在 C++(C11 之后也有 _Atomic
类型):
std::atomic<int> x;
x = 42; // 原子写
int y = x; // 原子读
原理:
- 编译器生成 CPU 原子指令(例如 x86 上的
LOCK MOV
或 ARM 的LDREX/STREX
) - 这些指令保证该操作不可被线程切换中断打断
- 因此 同一时间只有一个线程能成功完成操作,不需要
mutex
对比普通 C 变量:
int x;
x = 42; // 多线程同时写可能产生数据竞争
-
如果多个线程同时写
x
,CPU 执行的步骤可能是:- 线程1读取 x 到寄存器
- 线程2读取 x 到寄存器
- 两个线程都写回寄存器的值
-
这样最终值可能不等于任何一个线程写入的值 → 数据竞争
-
所以在 C 里通常需要
pthread_mutex_lock()
来保证互斥。
✅ 总结:std::atomic
内部已经实现了原子性保证,不需要显式锁。
2️⃣ 写入时会被其他线程“打断”吗?
-
不会,原子变量的单次操作是不可分割的(atomic)
-
如果两个线程同时写:
- CPU 会保证只有一个线程的写最终生效
- 另一线程会顺序执行
-
注意:这里指的是单次操作(比如单个
int
),多个操作组合就不是原子的,需要锁。
例如:
cpu_use_rate[0]++; // 原子加1
- 不会出现
++
中间步骤被切断的问题 - 如果普通 int,就可能出现两个线程同时读 → 两次加1结果只加1 的情况
3️⃣ 会被硬件中断打断吗?
-
普通 CPU 中断(IRQ):
- 中断可能在执行原子操作的过程中触发
- CPU原子指令保证原子性:即便中断发生,寄存器级操作不会被打断,操作会完整完成
- 中断结束后线程继续执行
-
多核系统:
- 原子操作一般会加锁总线或使用 CPU 内部机制,保证其他核看到的值是连续一致的
-
总结:CPU 级中断不会破坏原子操作的正确性,但 ISR 内如果同时访问相同变量,要小心竞态条件,可能需要 memory barrier。
4️⃣ C++ 原子 vs C 普通变量总结
特性 | C++ 原子 (std::atomic<int> ) | C 普通变量 (int ) |
---|---|---|
多线程读写是否安全 | ✅ 安全,单个变量原子性 | ❌ 不安全,可能数据竞争 |
是否需要锁 | ❌ 不需要(单变量操作) | ✅ 需要 mutex |
中断打断 | ❌ 不会被 CPU 原子指令打断 | ⚠️ 可能被上下文切换/线程抢占影响 |
多操作原子性 | ⚠️ 单次操作原子,多操作需锁 | ❌ 不保证 |
5️⃣ 关键知识点总结
- 原子操作保证单个变量读写完整性 → 不被线程切换打断
- 不需要 mutex → 性能高
- 多操作组合还是需要锁 → 例如同时写 CPU 和 RAM,占用率一致性
- 中断不会破坏原子操作,但 ISR 与线程访问同一个原子变量仍要注意顺序和屏障
如果你愿意,我可以画一个 多线程写 std::atomic<int>
的时间线图,直观展示为什么不会被打断,比文字讲更清楚。
你希望我画吗?