std::atomic<T> 是 C++11 引入的原子模板类 ,头文件:#include <atomic>
原子:一个操作不可被打断,CPU 一次性完成,不会出现 "读到一半、写到一半" 的中间状态。专门用来做多线程之间共享变量的安全读写。
核心解决两大问题
- 竞态条件(数据撕裂) 普通变量多线程同时读写,有可能读到半截值(比如 32int,写了低 16 位还没写高 16 位,读线程就拿走了)。
atomic保证读写操作是完整的,不会读到半拉数据。 - 内存可见性(CPU 缓存 / 编译器优化) 多核 CPU 每个核心有独立缓存;编译器会把变量优化放到寄存器,不一定每次都读写内存。 普通 bool:主线程写
flag=true,子线程可能一直读寄存器里旧的false,永远看不到更新。std::atomic自带内存屏障:强制读写直接同步到主内存,其他线程立刻看到最新值。
基础用法( std::atomic<bool>)
std::atomic<bool> m_bThreadExit;
m_bThreadExit = false; //赋值
m_bThreadExit = true;
while(!p->m_bThreadExit) //读取
✅ 读写不需要再加临界区 CCriticalSection。原子变量本身就是线程安全。
支持的基础类型
std::atomic<bool>、std::atomic<int>、std::atomic<unsigned int> 最常用。
不能随便放自定义类,一般只用基础类型。
常用成员函数
std::atomic<bool> flag{false};
bool v = flag.load(); //读取,等价于 bool v = flag;
flag.store(true); //写入,等价于 flag = true;
简写写法 flag=true / if(flag) 在工程里完全够用。
和 CCriticalSection(临界区)对比
- std::atomic
- 适合:简单标记(bool、计数器这类简单变量)
- 开销极小,无内核阻塞;纯用户态指令
- 局限:只能保护单个变量,不能保护一段代码、多个变量
- CCriticalSection(临界区)
-
适合:多变量、一段业务代码的互斥
-
可以包裹一大段代码,锁期间别的线程阻塞等待
-
开销比 atomic 大
-
m_bThreadExit:只是一个退出标记,用 std::atomic<bool> -
m_iSaveSOE:配合一段业务逻辑,读写成对,用 CCriticalSection theApp.m_lockSoe
⚠️ 重要误区
-
❌
std::atomic不能替代临界区处理多变量事务 比如同时修改 A、B 两个变量,atomic 只能保证 A 自身原子、B 自身原子,不能保证 A 和 B 同时一起更新。这种场景必须临界区。 -
❌
atomic<bool>只保证变量读写原子,不保护 if 里面后续代码if(flag)
{
//这里代码不再受atomic保护!
}
判断完成之后 flag 立刻可能被别的线程修改,这是正常现象。
和 volatile 的区别(工控老项目常混淆)
volatile bool flag;
volatile:只禁止编译器优化,每次强制读内存 ,不提供多核 CPU 缓存同步,多核环境有概率失效。std::atomic:同时做到:禁止编译器优化 + CPU 缓存同步 + 操作原子。
所以 C++11 以后,多线程共享标记优先 atomic,volatile 只用于硬件寄存器访问。
举个简化示例,模拟的线程退出场景
#include <atomic>
#include <windows.h>
#include <Afxwin.h>
std::atomic<bool> g_exit = false;
UINT ThreadFunc(LPVOID p)
{
while(!g_exit)
{
Sleep(10);
TRACE(_T("running\n"));
}
TRACE(_T("thread exit\n"));
return 0;
}
主线程设置 g_exit=true; 子线程马上感知到并退出。
一句话总结
std::atomic<T> 就是多线程共享单个简单变量的轻量级线程安全包装,不用锁,保证变量读写完整、内存可见。 用来做线程退出标记,属于 atomic 最经典、最合适的使用场景。