C++中的无锁栈弹出实现

use*_*333 9 c++ concurrency multithreading lock-free data-structures

在 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;
    }
};
Run Code Online (Sandbox Code Playgroud)

重要的是,每次调用 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

此处调用的其他函数的实现无关紧要(它们只是将节点添加到要删除的节点链中),因此我省略了它们。我在代码中感到困惑的部分是#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 呢?有人可以解释一下吗?

Mat*_*ans 4

调用的线程try_reclaim刚刚old_head从堆栈中删除。

该类确保 的任何其他使用都old_head必须在pop其他线程的调用内部,因此如果该线程发现没有其他并发调用,那么它就知道它是该指针的独占持有者old_head。然后,只要它不发布该指针以便可以从另一个线程中获取它,它就可以在它访问该指针时将其删除。

所以实施是安全的。你问的问题:“他为什么不[再次]检查”表明你的想法是错误的。再次检查并不能证明任何事情,因为如果另一个线程有可能进入pop并使用old_head,那么它总是可能在您检查发生!