C++ 原子锁知识点介绍 std::atomic<int>

好的,我们用 教科书级别 的方式来讲解 std::atomic<int> 以及你提到的数组用法,并给出完整案例。


1️⃣ 基础概念:原子(Atomic)

  • 原子操作:操作要么全部完成,要么完全不发生,中间不会被线程切换打断。
  • 在多线程环境下,如果两个线程同时写同一个普通变量,会产生 数据竞争(Data Race)
  • std::atomic<T> 就是 C++ 提供的标准库原子类型,用来保证单个变量的操作在多线程下线程安全

关键特点:

  1. 不需要手动加锁 mutex
  2. 对于基本类型(如 int、bool、指针)性能更高。
  3. 只保证单个变量原子性,不能保证多个变量一起原子更新(那就需要 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;
}

说明

  1. cpu_use_rate 是原子数组,每个线程独立修改自己的核占用率。
  2. 没有 mutex,不会产生数据竞争。
  3. 性能比加锁高,尤其适合单个整型变量频繁读写的场景。

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. 线程1读取 x 到寄存器
    2. 线程2读取 x 到寄存器
    3. 两个线程都写回寄存器的值
  • 这样最终值可能不等于任何一个线程写入的值 → 数据竞争

  • 所以在 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️⃣ 关键知识点总结

  1. 原子操作保证单个变量读写完整性 → 不被线程切换打断
  2. 不需要 mutex → 性能高
  3. 多操作组合还是需要锁 → 例如同时写 CPU 和 RAM,占用率一致性
  4. 中断不会破坏原子操作,但 ISR 与线程访问同一个原子变量仍要注意顺序和屏障

如果你愿意,我可以画一个 多线程写 std::atomic<int> 的时间线图,直观展示为什么不会被打断,比文字讲更清楚。

你希望我画吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值