std::stack 默认非线程安全,因其为无锁容器适配器,push/pop/top 等操作不具原子性,多线程并发访问会导致未定义行为;加 mutex 虽可行但有性能与 ABA 风险;高并发场景应选用经验证的 lock-free 实现如 boost::lockfree::stack。

为什么 std::stack 默认不是线程安全的
因为 std::stack 本身只是容器适配器,底层用 std::deque 或 std::vector 实现,所有操作(push()、pop()、top())都不带原子性或锁保护。多个线程同时调用 push() 可能导致底层容器重分配时迭代器失效,同时读写 top() 和 pop() 会引发数据竞争 —— 这不是“偶尔出错”,而是未定义行为(UB),崩溃、静默错误、内存越界都可能。
用 std::mutex + std::stack 能 work,但有性能陷阱
加互斥锁最直接,但容易忽略两点:一是锁粒度太大(整个栈生命周期被一把锁串行化),二是 top() + pop() 的分离调用天然存在 ABA 风险和条件竞争。
-
top()返回引用,若在锁外使用该引用,对象可能已被其他线程pop()销毁 - 即使加锁,
if (!s.empty()) { auto x = s.top(); s.pop(); }在锁内仍是两步操作,中间无法插入原子判断 - 高争用下,
std::mutex会导致大量线程阻塞、上下文切换开销飙升
所以生产环境更倾向无锁(lock-free)实现,或至少提供原子的 try_pop() 接口。
基于 std::atomic + CAS 实现 lock-free ConcurrentStack
核心是用单向链表 + 原子指针(std::atomic<node></node>)管理栈顶,所有操作通过 compare_exchange_weak() 循环重试完成。关键点不在“是否用了 CAS”,而在于如何避免 ABA 和内存回收问题。
立即学习“C++免费学习笔记(深入)”;
- 节点必须用
new分配(不能栈分配),且 pop 后不能立即delete—— 需配合 hazard pointer 或 epoch-based reclamation(EBR),否则可能访问已释放内存 - push 操作中,先设置新节点的
next指向当前栈顶,再用 CAS 更新栈顶;失败则重试,无需锁 - pop 操作需一次性读取栈顶并更新其 next,CAS 失败说明栈顶被其他线程修改,继续循环
- 为避免 ABA,可对指针做“版本号打包”(如
std::atomic<uint64_t></uint64_t>存低 48 位指针 + 高 16 位计数),但 C++20 起推荐用std::atomic<:shared_ptr></:shared_ptr>简化内存管理
简化版(不处理 ABA,仅用于教学理解):
template<typename T>
class ConcurrentStack {
struct Node { T data; Node* next; };
std::atomic<Node*> head{nullptr};
<p>public:
void push(const T& x) {
Node<em> node = new Node{x, nullptr};
Node</em> expected;
do {
expected = head.load();
node->next = expected;
} while (!head.compare_exchange_weak(node->next, node));
}</p><pre class="brush:php;toolbar:false;">bool try_pop(T& x) {
Node* expected;
do {
expected = head.load();
if (!expected) return false;
} while (!head.compare_exchange_weak(expected, expected->next));
x = expected->data;
delete expected;
return true;
}};
实际项目中别手写 lock-free stack
真正需要高性能并发栈的场景(如 lock-free MPSC 队列、内存池分配器),应优先考虑经过充分测试的工业级实现:比如 boost::lockfree::stack(支持固定容量与动态增长)、Folly::MPMCQueue 的变体,或基于 libcds 的容器。自己实现时最容易翻车的是内存回收逻辑 —— 即使 CAS 正确,漏掉 hazard pointer 注册或 EBR epoch 切换,就会在压力测试后期随机 crash。
如果你只是想让多个线程安全地共享一个栈,且吞吐量不高,用 std::mutex 包裹 std::stack 并提供 pop_or_wait() 封装,反而更可靠、更易调试。


















