C++编程指南31 - 除非绝对必要,否则不要使用无锁编程
一: 概述:
无锁编程容易出错,并且需要专家级的知识,包括:
-
语言特性(如 C++ 的
std::atomic
) -
计算机架构(如 CPU 缓存一致性协议)
-
数据结构(如无锁队列、无锁栈)
二:示例
extern atomic<Link*> head; // 共享链表的头指针
Link* nh = new Link(data, nullptr); // 为插入准备一个新节点
Link* h = head.load(); // 读取共享的头指针
do {
if (h->data <= data) break; // 如果不满足插入条件,则退出
nh->next = h; // 让新节点指向当前的头节点
} while (!head.compare_exchange_weak(h, nh)); // 尝试把 nh 设置为新的头节点
-
这里存在 ABA 问题,即
h
可能在compare_exchange_weak
过程中被其他线程修改并恢复,导致compare_exchange_weak
误以为h
未变,进而错误地插入nh
。 -
这种错误很难通过测试发现,因为并发问题通常是非确定性的。
三:总结
高层次的并发机制(如 线程 和 互斥锁)本质上是基于无锁编程实现的,但它们隐藏了底层的复杂性,使开发者更容易正确使用。如果必须使用无锁数据结构,优先使用现成的库,例如:
-
folly::AtomicLinkedList
(Facebook 开源库)实现了无锁链表 -
libcds
提供成熟的无锁数据结构(队列、哈希表等)