std::atomic<bool>与bool的区别
1. 根本区别
特性 | std::atomic<bool> | 普通 bool |
---|---|---|
原子性 | ✅ 保证读/写是原子操作 | ❌ 不保证原子性 |
内存顺序 | ✅ 可控制内存访问顺序 | ❌ 无保证 |
线程安全 | ✅ 多线程读写安全 | ❌ 需要额外同步 |
编译器优化屏障 | ✅ 阻止不安全的指令重排 | ❌ 无屏障作用 |
2. 为什么普通 bool 不够?
(1) 撕裂问题 (Tearing)
- 在32+位系统上,
bool
可能被编译器优化为位域操作 - 非原子操作可能导致部分写入(如同时读写时看到半旧半新的值)
(2) 内存可见性问题
// 线程A
should_exit = true; // 普通bool写入可能缓存在CPU核心本地缓存// 线程B
while(!should_exit) { /*...*/ } // 可能永远看不到线程A的修改
(3) 指令重排风险
// 编译器/CPU可能重排指令
init_resources();
should_exit = false; // 普通bool可能被重排到init_resources()之前
3. std::atomic<bool>
的保障
(1) 硬件级原子性
- x86: 通过
LOCK
前缀指令保证总线锁定 - ARM: 使用
LDREX/STREX
指令实现原子操作
(2) 内存顺序控制
std::atomic<bool> flag{false};// 线程A
flag.store(true, std::memory_order_release);// 线程B
while(!flag.load(std::memory_order_acquire)) { /*...*/ }
(3) 编译器屏障
阻止跨原子变量的指令重排:
// 保证顺序性
a = 1;
flag.store(true); // 编译器不会将这两行调换顺序
b = 2;
4. 性能对比
操作 | x86-64 (ns/op) |
---|---|
普通 bool 写 | ~0.3 |
atomic<bool> 写 | ~5-20 |
带内存屏障的原子写 | ~20-100 |
5. 何时可以用普通 bool?
仅在以下场景可安全使用:
- 单线程环境
- 多线程只读不写
- 已有其他同步机制(如互斥锁保护)
6. 最佳实践示例
(1) 退出标志
std::atomic<bool> should_exit{false};// 控制线程
void ctrl_thread() {std::cin.get(); // 等待输入should_exit.store(true);
}// 工作线程
void worker_thread() {while(!should_exit.load()) {// 处理任务}
}
(2) 双重检查锁定
std::atomic<bool> initialized{false};
std::mutex mtx;void lazy_init() {if(!initialized.load(std::memory_order_acquire)) {std::lock_guard<std::mutex> lock(mtx);if(!initialized) {init_resources();initialized.store(true, std::memory_order_release);}}
}
7. 各平台实现差异
平台 | 原子性实现方式 |
---|---|
x86-64 | MOV 指令直接支持字节级原子操作 |
ARMv8 | 需要显式使用 LDAXR/STLXR 指令 |
嵌入式MCU | 可能禁用缓存,依赖总线锁 |
总结:std::atomic<bool>
是线程安全的,而普通 bool
不是。在涉及多线程共享的标志变量时,必须使用原子类型以避免未定义行为。虽然原子操作有性能开销,但这是保证正确性的必要代价。