《C++ Concurrency in Action》拆分引用计数示例技术疑问
《C++ Concurrency in Action》第二版无锁栈实现问题解答
代码片段
template<typename T> class lock_free_stack { private: struct node; struct counted_node_ptr { int external_count; node* ptr; }; struct node { std::shared_ptr<T> data; std::atomic<int> internal_count; counted_node_ptr next; node(T const& data_): data(std::make_shared<T>(data_)), internal_count(0) {} }; std::atomic<counted_node_ptr> head; public: ~lock_free_stack() { while(pop()); } void push(T const& data) { counted_node_ptr new_node; new_node.ptr=new node(data); new_node.external_count=1; new_node.ptr->next=head.load(); while(!head.compare_exchange_weak(new_node.ptr->next,new_node)); } };
Q1:push方法中,新创建的counted_node_ptr分配在栈上,push()返回后head是否会成为悬空指针?是否存在理解遗漏?
不会。这里的核心是区分counted_node_ptr本身的存储和它内部ptr指向的对象:
- 栈上的
new_node是临时的counted_node_ptr实例,它的ptr指向堆上动态分配的node对象; head是原子类型的counted_node_ptr变量,调用compare_exchange_weak时,会把new_node的内容(包括external_count和ptr的值)完整复制到head中;- 当
push返回后,栈上的new_node被销毁,但head中存储的是独立的副本,其ptr依然指向堆上的node,因此head不会成为悬空指针。
常见的理解遗漏是混淆了“计数结构的存储位置”和“链表节点的存储位置”——临时变量只是传递节点信息的载体,真正的链表节点在堆上,head保存的是节点的引用信息副本,和临时变量无关。
Q2:书中双引用计数机制中,external_count与internal_count之和为节点总引用数。为何单计数器不足以实现?两者分别代表什么?
单计数器的局限性
在无锁并发场景下,单引用计数无法区分两种关键的引用类型:
- 一种是链表结构中持久存在的引用(比如
head或其他节点的next指向该节点); - 另一种是线程操作过程中持有的临时引用(比如线程加载了节点指针,正在进行修改或读取操作)。
如果只用单计数器,当线程尝试删除节点时,可能出现其他线程还持有临时引用的情况,导致内存提前释放;或者无法准确判断节点是否真正无人使用,造成内存泄漏。
双计数器的含义
- external_count:代表链表结构中指向该节点的外部引用数量。每个
counted_node_ptr实例中的external_count就是对该节点的一次外部引用——比如head指向节点时,对应external_count为1,后续如果有新节点插入到它前面,新节点的next也会持有该节点的一次外部引用。 - internal_count:代表线程正在持有该节点的临时引用数量。当线程需要操作某个节点时,会先复制节点的指针,此时
internal_count递增,用来跟踪这些未完成的操作;当操作完成后,internal_count递减。
只有当external_count + internal_count的总和为0时,才说明该节点既没有被链表结构引用,也没有线程在操作它,可以安全销毁。
内容的提问来源于stack exchange,提问作者Yariv Aridor
相关产品推荐
相关产品推荐

