由于ABA问题,这个危险指针示例是否有缺陷?

时间:2018-02-14 09:26:01

标签: c++ c++11 concurrency lock-free aba

在书C++ Concurrency in Action中,作者举了一个使用危险指针来实现无锁堆栈数据结构的例子。部分代码如下:

std::shared_ptr<T> pop()
{
    std::atomic<void*>& hp=get_hazard_pointer_for_current_thread();
    node* old_head=head.load();
    node* temp;
    do
    {
        temp=old_head;
        hp.store(old_head);
        old_head=head.load();
    } while(old_head!=temp);
    // ...
}

描述说

  

您必须在while循环中执行此操作,以确保node没有   在读取旧head指针和之后删除了   设置危险指针。在这个窗口期间没有其他线程   知道你正在访问这个特定的节点。幸运的是,如果老了   head节点将被删除,head本身必须已更改,   所以你可以检查这个并继续循环,直到你知道head   指针仍然具有您设置危险指针的相同值。

我认为代码存在缺陷,因为head节点受ABA problem的约束。即使head的值保持不变,它最初指向的节点也可能已被删除。分配了一个新的head节点,该节点恰好具有与前一个节点相同的地址值。

1 个答案:

答案 0 :(得分:5)

load()操作的默认memory_orderstd::memory_order_seq_cst,可确保所有操作的顺序一致性(全局排序总数):

  

从原子变量加载的每个memory_order_seq_cst操作B   M,观察以下其中一项:

     
      
  • 修改A的上一次操作M的结果,该操作在单个总订单中显示在B之前
  •   
  • 或者,如果有这样的AB可能会在M memory_order_seq_cst上观察到某些修改的结果,而不是A   发生在A
  • 之前   
  • 或者,如果没有这样的BM可能会观察到memory_order_seq_cst std::memory_order_seq_cst的{​​{1}}的某些无关修改的结果。
  •   

因此,如果节点被修改(删除)并且这发生在全局全局顺序中的第二次读取之前,则可以保证看到该更改,因此循环将继续执行。如果在此之后订购此修改,则因为已经设置了危险指针,所以没有任何危害。

您有此保证,因为存储到危险指针也是使用old_head==temp完成的。此内存顺序为存储提供获取操作以及 release 操作,从而防止在同一线程内重新排序。因此,“成功”读取(pop())可确保保存正确的数据。

将这两个加载视为同步点 - 因为它们执行获取操作,它们与相应的 release 操作同步,这些操作会修改这些值,导致所有写入变为可见。

您描述的问题不会以任何方式破坏该示例。 concurrent_stack<int> p; if (!p.empty() && (p.top() == 5)) { auto t = p.pop(); assert( t ); // May fail assert( *t == 5 ); // May fail } 函数旨在删除顶部元素,它将执行此操作。如果在此期间添加/删除元素,它将弹出它,无论它的地址是什么(它甚至可能与先前获取的地址相同)。这是一个完全不同的问题。考虑:

pop()

两个断言都可能失败,如果许多线程非常密集地使用堆栈,很可能会经常失败。但这不是由于=SUM(IF(FREQUENCY(IF(B2:B10=C1,IF(A2:A10<>"",MATCH(A2:A10,A2:A10,0))),ROW(A2:A10)-ROW(A2)+1),1)) 的错误实现,而是因为您需要更强的访问限制以确保最后一个已检查的元素确实从堆栈中删除。