【问题标题】:It is possible to getting stuck in the compare_exchange's loop?有可能陷入 compare_exchange 的循环吗?
【发布时间】:2020-03-30 13:17:43
【问题描述】:

考虑这段代码(来自'C++ concurrency in action' [第二版]:第 212 页):

void LockFreeStack::pop (stack_data& result)
{
    Node* old_head = head.load();
    while(!head.compare_exchange_weak(old_head, old_head->next))
        ;
    result = old_head->data;
}

我认为线程一个执行pop() 是可能的,并且在执行第一行(加载head)后,时间切片发生在线程二上,它正在执行push(T&)。所以现在head 原子变量的值不等于old_head 并且while-loop 不会中断。
对吗?

【问题讨论】:

  • compare_exchange_weak 更新其第一个参数以匹配第二个。这就是打破循环的原因。

标签: c++ atomic gcc9


【解决方案1】:

假设headstd::atomic<Node*>,那么代码是正确的,因为当compare_exchange_weak 失败时,它将变量的当前值加载到它的第一个参数中,在compare_exchange_weak 调用之后old_head 将始终包含当前head 的值。

代码大致相当于在没有原子的情况下执行以下操作:

Node* old_head = head;
while (true)
{
  std::unique_lock lock(mutex);
  if (old_head == head)
  {
     head = old_head->next;
     break;
  }
  else
  {
     old_head = head;
  }
}

因此,对pop 的并发调用是安全的,不应永远阻塞(除非其他线程不断调用 pop 并且总是在当前线程有机会之前设置值)。

【讨论】:

    【解决方案2】:

    正如@Alan Birtles 已经解释的那样,您不会被困在 while 循环中。但是,重要的是要注意您的代码很可能会遇到 ABA 问题。考虑以下场景:

    • 堆栈如下所示:A->B->C(即,头指向 A)
    • 线程 1 加载头部(即A 的地址)和A 的下一个指针(B),但在执行 CAS 之前,它被中断了。
    • 线程 2 弹出A,然后弹出B,然后压入A,即堆栈现在看起来像这样:A->C
    • 线程 1 恢复并愉快地将头部从 A 更新为 B -> boom!

    有几种可能的解决方案可以避免 ABA 问题,例如标记指针或并发内存回收方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-09
      • 2020-06-09
      • 2010-11-09
      • 1970-01-01
      • 2011-05-05
      • 2019-11-12
      • 2017-03-05
      • 2019-03-03
      相关资源
      最近更新 更多