具有引用计数的无锁堆栈
Lock-free stack with reference counting
来自我上一个问题的答案: 删除后的 c++ 中的指针
很明显,使用指向"已删除"内存(特别是复制它们)的指针值会导致 C++11 中的 undef 行为,以及 C++14 中的实现定义行为。
Antomy Williams 在他的书"C++并发在行动"中提出了下一个具有引用计数的无锁堆栈:
#include <atomic>
#include <memory>
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;
void increase_head_count(counted_node_ptr& old_counter)
{
counted_node_ptr new_counter;
do
{
new_counter=old_counter;
++new_counter.external_count;
}
while(!head.compare_exchange_strong(
old_counter,new_counter,
std::memory_order_acquire,
std::memory_order_relaxed));
old_counter.external_count=new_counter.external_count;
}
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(std::memory_order_relaxed)
while(!head.compare_exchange_weak(
new_node.ptr->next,new_node,
std::memory_order_release,
std::memory_order_relaxed));
}
std::shared_ptr<T> pop()
{
counted_node_ptr old_head=
head.load(std::memory_order_relaxed);
for(;;)
{
increase_head_count(old_head);
node* const ptr=old_head.ptr;
if(!ptr)
{
return std::shared_ptr<T>();
}
if(head.compare_exchange_strong(
old_head,ptr->next,std::memory_order_relaxed))
{
std::shared_ptr<T> res;
res.swap(ptr->data);
int const count_increase=old_head.external_count-2;
if(ptr->internal_count.fetch_add(
count_increase,std::memory_order_release)==-count_increase)
{
delete ptr;
}
return res;
}
else if(ptr->internal_count.fetch_add(
-1,std::memory_order_relaxed)==1)
{
ptr->internal_count.load(std::memory_order_acquire);
delete ptr;
}
}
}
};
我担心功能
void increase_head_count(counted_node_ptr& old_counter)
{
counted_node_ptr new_counter;
do
{
new_counter=old_counter;
++new_counter.external_count;
}
while(!head.compare_exchange_strong(
old_counter,new_counter,
std::memory_order_acquire,
std::memory_order_relaxed));
old_counter.external_count=new_counter.external_count;
}
似乎,当old_counter.ptr已被另一个线程删除时,可以执行new_counter=old_counter;。
那么,有人可以确认或拒绝,这个堆栈实现在 c++11 中是完全不正确的吗?
我认为实现还有其他问题: 让我们假设两个线程正在处理一个非空的无锁堆栈:
- 线程 A 调用 push() 在代码行之后添加新节点 new_node.ptr->next=head.load(std::memory_order_relaxed);已经 执行后,线程 A 进入睡眠状态;
- 线程 B 调用 pop() 以删除代码行之后的旧节点 increase_head_count(old_head);已执行,线程 B 进入 睡;
- 线程 A 继续运行,发现头节点的外部引用计数不是 1,但信息会被忽略,新节点会作为新头添加到堆栈中;
- 线程 B 继续运行,head.compare_exchange_strong() 会失败,ptr->internal_count.fetch_add(-1, std::memory_order_relaxed) 会被执行,导致旧头指针的外部引用计数仍然是 2,但内部引用计数是 -1。
- 所以无锁堆栈坏了!
谁可以帮助检查这是否是一个真正的问题?
这是可能的,但没有问题,因为compare_exchange
调用将检测到它并丢弃new_counter
。
相关文章:
- 拥有映射的现代方法,该映射可以指向或引用已在堆栈上分配的不同类型的数据
- 为什么 STL 容器适配器堆栈中的 top 返回常量引用?
- 在 gtest 中初始化堆栈上的引用变量的隔离错误
- 如果我们通过引用传递变量,则递归中使用的堆栈空间量是否为零?
- 了解通过引用传递取消引用指针时C++堆/堆栈分配
- 如何返回堆栈 c++ 中顶部对象的引用
- 递归中的 C++ 引用(反转堆栈)
- 具有引用计数的无锁堆栈
- 警告#13212:引用需要堆栈对齐功能的EBX
- 未定义对调用堆栈库的引用出现问题
- 堆栈在函数中弹出仍然显示在主函数中。调用不应该通过引用给定的向量吗
- 返回可能在C++中分配在堆栈上的成员时,最佳指针/引用类型是什么
- C++按引用传递:如何使用调用堆栈
- 通过引用传递大型对象时堆栈溢出
- 尝试实现堆栈时C++未定义的引用
- C++11 Lambda闭包通过引用涉及一个堆栈变量,该变量离开作用域是允许的,但得到了未定义的行为
- 如果使用alloca在内联函数中的堆栈上分配变量,那么在内联函数返回后,其引用是否有效
- Visual Studio 在引用堆栈变量时不使用 EBP
- 引用变量是否占用堆栈中的内存
- 如何在C++11中声明堆栈引用