基于原子变量的简单自旋锁是否优于该无锁队列实现?
关于无锁队列实现的疑问解答
1. 你提到的Medium示例实现并不优秀,甚至存在明显bug
这个push方法里有个致命问题:循环内部重新声明了old_tail变量,导致外部的old_tail始终是初始加载的旧值,最后一步tail.compare_exchange_weak(old_tail, new_node)几乎必然失败——此时的old_tail早就不是当前队列的尾节点了。正确的写法应该是更新外部的old_tail,而非重新定义:
void push(T const& data) { std::atomic<node*> const new_node=new node(data); node* old_tail = tail.load(); while(!old_tail->next.compare_exchange_weak(nullptr, new_node)){ old_tail = tail.load(); // 去掉node*,更新外部变量 } tail.compare_exchange_weak(old_tail, new_node); }
除此之外,这个实现还存在内存泄漏(pop后的旧节点未回收)、未处理ABA问题、pop操作在并发场景下的空指针风险等缺陷,完全算不上合格的工业级无锁队列实现。
2. 自旋锁和无锁队列的核心差异
你用原子标志实现的自旋锁确实简单易懂,但二者的设计目标和适用场景天差地别:
- 自旋锁是阻塞式同步:本质是通过互斥保证同一时间只有一个线程能操作队列。高并发下,大量线程会原地自旋等待,浪费CPU资源;如果线程被操作系统调度挂起,之前的自旋时间完全白费,还会带来上下文切换的开销。适合低并发、临界区操作极短的场景。
- 无锁队列是非阻塞式同步:利用CAS操作让多个线程同时尝试更新共享状态,失败的线程不需要挂起,直接重试(或去做其他任务)。理论上在高并发场景下吞吐量更高,因为没有锁的开销,但实现难度极大——要处理CAS的ABA问题、节点内存安全回收、并发push/pop的边界冲突等各种细节。
3. 选择建议
- 如果你的场景是低并发、对实现复杂度敏感,自旋锁完全够用,简单易维护是很大的优势。
- 如果需要在高并发场景下追求性能,得用成熟的无锁队列实现,比如经典的Michael-Scott无锁队列(解决了tail更新、内存回收等核心问题),而非这个有bug的Medium示例。
内容的提问来源于stack exchange,提问作者Zebrafish
相关产品推荐
相关产品推荐

