最简线程安全计数器应直接使用 std::atomic,它基于 CPU 原子指令,无锁高效;声明为 std::atomic<int> count{0}; 即可,自增用 ++count 或 fetch_add,读取用 load(),避免裸 int 或冗余 mutex;仅当需“读-改-写”复合逻辑(如条件更新)时才改用 std::mutex。

用 std::atomic 实现最简线程安全计数器
直接上手就用 std::atomic<int></int>,别自己加锁封装——它专为这种场景设计,底层是 CPU 原子指令,无锁、高效、天然线程安全。
常见错误是把 int 成员变量裸露给多线程读写,或者用 std::mutex 包一层普通 int,徒增开销且易漏锁。
-
std::atomic<int> count{0};</int>声明即安全,初始化支持常量表达式 - 自增用
count.fetch_add(1, std::memory_order_relaxed)或更简洁的++count(等价于fetch_add(1, memory_order_seq_cst)) - 读取用
count.load(),不要直接用count(虽部分编译器允许,但语义不清晰) - 避免混用不同内存序:默认
memory_order_seq_cst最安全;若性能敏感且逻辑明确,可降级为relaxed,但需确保无依赖关系
什么时候必须用 std::mutex 而不是 std::atomic
原子类型只保单个操作的线程安全。一旦涉及「读-改-写」复合逻辑(比如“仅当值小于 100 时才加 1”),std::atomic 的 compare_exchange_weak 虽能做,但代码变复杂、易出错;此时该换锁。
- 典型场景:带条件更新(
if (count )——这三步不是原子的,必须用 <code>std::mutex包住整个块 - 多个变量需同步更新(如计数器 + 时间戳),无法靠单个原子变量保证一致性
- 需要阻塞等待(如“直到计数器达到 5 才继续”),得配合
std::condition_variable,这时锁是基础设施
std::atomic 的陷阱:对齐与拷贝
看似简单的类型,实际有隐藏约束。编译器可能因对齐问题让 std::atomic<t></t> 失去原子性——尤其在自定义结构体里嵌套时。
立即学习“C++免费学习笔记(深入)”;
- 基础类型(
int、long long)通常安全;但std::atomic<bool></bool>在某些平台可能被实现为锁保护,性能不如预期 -
std::atomic不可拷贝(删除了拷贝构造/赋值),只能移动或赋值(=是 store 操作,不是拷贝) - 禁止用
memcpy或reinterpret_cast绕过原子接口访问其内部存储——行为未定义 - 调试时注意:GDB 可能无法直接打印
std::atomic值,得调用.load()查看
计数器值溢出怎么办
原子类型不处理溢出逻辑。例如 std::atomic<uint8_t></uint8_t> 加到 255 后再 ++,结果是 0 —— 这是标准定义的回绕行为,不是 bug。
- 若业务不允许溢出(如请求 ID 必须唯一递增),选足够大的类型:
std::atomic<uint64_t></uint64_t>基本可忽略溢出风险 - 若需检测溢出,不能依赖
operator++,得用fetch_add检查返回值:auto old = counter.fetch_add(1); if (old == std::numeric_limits<decltype(old)>::max()) { // 处理溢出 } - 无符号类型回绕是确定行为;有符号溢出是未定义行为,绝对不要用
std::atomic<int></int>做计数器并任其溢出


















