【发布时间】:2020-09-14 23:18:43
【问题描述】:
在 C++ Concurrency in Action, 2e 中,作者描述了一种无锁线程安全链表实现。现在,他正在描述 pop() 方法以及如何以一种类似“垃圾收集器”的方法安全地删除节点,以确保没有其他线程在同一实例上调用 pop。这是pop的一些代码:
#include <atomic>
#include <memory>
template<typename T>
class lock_free_stack
{
private:
std::atomic<unsigned> threads_in_pop;
void try_reclaim(node* old_head);
public:
std::shared_ptr<T> pop()
{
++threads_in_pop;
node* old_head=head.load();
while(old_head &&
!head.compare_exchange_weak(old_head,old_head->next));
std::shared_ptr<T> res;
if(old_head)
{
res.swap(old_head->data);
}
try_reclaim(old_head);
return res;
}
};
重要的是每次调用 pop() 时计数器都会自动递增。然后,try_reclaim 函数将递减所述计数器。下面是 try_reclaim 的实现:
void try_reclaim(node* old_head)
{
if(threads_in_pop==1) //#1
{
node* nodes_to_delete=to_be_deleted.exchange(nullptr);
if(!--threads_in_pop) //#2
{
delete_nodes(nodes_to_delete);
}
else if(nodes_to_delete)
{
chain_pending_nodes(nodes_to_delete);//#3
}
delete old_head; //#4 THIS IS THE PART I AM CONFUSED ABOUT
}
else
{
chain_pending_node(old_head);
--threads_in_pop;
}
}
这里调用的其他函数的实现是无关紧要的(它们只是将节点添加到要删除的节点链中),所以我省略了它们。我在代码中感到困惑的部分是#4(标记)。这里,作者对传入的old_head调用了delete。但是,为什么不在删除old_head之前检查一下threads_in_pop此时是否仍然为零呢?他在第 2 行和第 1 行再次检查以确保另一个线程当前不在 pop() 中,那么他为什么不在继续删除 old_head 之前再次检查呢?难道另一个线程不可能在#3 之后立即调用pop(),从而增加计数器,当第一个线程到达#4 时,threads_in_pop 将不再为零?
也就是说,在代码到达#4时,threads_in_pop不可能是例如2吗?在那种情况下,他怎么能安全地删除 old_head 呢?有人可以解释一下吗?
【问题讨论】:
标签: c++ multithreading data-structures concurrency lock-free