C++ 学习(9)——多线程(2)--原子操作
原子变量 std::atomic
🌟 目标:理解数据竞争的根源,掌握原子操作的语义,学会使用
std::atomic来构建无锁线程安全程序。
📚 一、课程目标
- 理解什么是“原子性”和“数据竞争”
- 掌握
std::atomic的使用方式和常见操作 - 了解原子变量与互斥锁的区别与联系
- 理解常见原子操作:
store()、load()、fetch_add()等
🧠 二、理论内容
1. 什么是原子操作?
- 原子操作:不可被中断的操作,要么全部完成,要么完全不发生。
- 多线程环境中,如果多个线程读写同一个变量,且没有同步机制,就会发生数据竞争,导致未定义行为。
std::atomic提供原子性保障,防止数据竞争。
2. 使用 std::atomic 的基本语法
#include <atomic>
std::atomic<int> counter(0);
支持的操作:
counter++; // 原子自增
counter--; // 原子自减
counter.store(5); // 原子写
int x = counter.load(); // 原子读
counter.fetch_add(3); // 原子加法
3. 原子变量替代互斥锁的场景
| 场景 | 推荐用法 |
|---|---|
| 仅一个整型计数器或标志位 | ✅ std::atomic |
| 涉及多个变量一致性修改 | ❌ std::mutex 更合适 |
| 大量频繁读写的共享计数 | ✅ 原子效率高 |
4. 原子操作是线程安全的吗?
是的!但:
std::atomic只能保障它自身的线程安全;- 多个变量之间的同步仍然需要其他手段,比如锁或条件变量。
5. 原子变量的内存序(了解即可)
counter.store(5, std::memory_order_relaxed);
- 默认使用
memory_order_seq_cst,强顺序; relaxed可以提升性能,但程序设计更复杂(需谨慎使用)。
🧪 三、实战示例
示例:原子计数器替代互斥锁
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> counter(0);
void increase() {
for (int i = 0; i < 10000; ++i) {
counter++;
}
}
int main() {
std::thread t1(increase);
std::thread t2(increase);
t1.join();
t2.join();
std::cout << "Final counter: " << counter.load() << std::endl;
return 0;
}
🔎 输出一定是 20000,无数据竞争!
📋 四、练习题
💡 练习 1:多线程原子加法
- 编写一个使用
std::atomic<int>的计数器程序; - 启动 4 个线程,每个线程自增 10000 次;
- 最终输出应为
40000。
💡 练习 2:对比互斥锁版本
- 用
std::mutex和普通int写同样的程序; - 对比两者的执行速度(可使用
std::chrono::steady_clock测量时间); - 思考在实际项目中什么时候该选原子,什么时候选锁?
💡 思考题
std::atomic<int>能否完全替代std::mutex?- 如果你要对两个变量一起做加法操作,是否还能使用原子?
🧠 小结
| 知识点 | 说明 |
|---|---|
| 原子性 | 操作不可被中断 |
| std::atomic | 提供无锁的线程安全基本类型 |
| 使用时机 | 单变量读写频繁,且线程竞争不严重 |
| 限制 | 多变量一致性修改需锁 |
需要我为 Day 6 安排课程吗?或者帮你出练习题答案也可以~

浙公网安备 33010602011771号