简单的无锁堆栈c ++ 11

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单的无锁堆栈c ++ 11相关的知识,希望对你有一定的参考价值。

我已经看到了几个过于复杂的(在我看来很明显)在c ++中实现了一个无锁堆栈(使用像here这样的标签),我想出了一个简单但仍然有效的实现。因为我无法在任何地方找到这个实现(我已经看到Push函数的实现类似于我所做的而不是Pop),我猜它在某种程度上是不正确的(很可能是ABA案例失败):

template<typename Data>
struct Element
{
  Data mData;
  Element<Data>* mNext;
};

template<typename Data>
class Stack
{
 public:
  using Obj = Element<Data>;
  std::atomic<Obj*> mHead;

  void Push(Obj *newObj)
  {
    newObj->mNext = mHead.load();
    //Should I be using std::memory_order_acq_rel below??
    while(!mHead.compare_exchange_weak(newObj->mNext, newObj));
  }

  Obj* Pop()
  {
    Obj* old_head = mHead.load();
    while (1)
    {
      if (old_head == nullptr)
        return nullptr;
      //Should I be using std::memory_order_acq_rel below??
      if(mHead.compare_exchange_weak(old_head, old_head->mNext)) ///<<< CL1
        return old_head;
    }
  }
};

我假设Push和Pop的调用者将负责内存分配和释放。另一种选择是制作上述Push和Pop私有方法,并使用新的公共函数来处理内存分配并在内部调用这些函数。我相信这个实现中最棘手的部分是我用“CL1”标记的行。我认为它是正确的并且仍然适用于ABA案例的原因如下:

让我们说ABA案件确实发生了。这意味着“CL1”处的mHead将等于old_head,但是它们指向的对象实际上与我将mHead分配给它时最初指向的old_head不同。但是,我认为即使它是一个不同的对象,我们仍然可以,因为我们知道它是一个有效的“头”。 old_head指向与mHead相同的对象,因此它是堆栈的有效头部,这意味着old_head-> mNext是有效的下一个头。因此,将mHead更新为old_head-> mNext仍然是正确的!

总结一下:

  1. 如果mHead!= old_head(另一个线程抢占我们并更改了mHead) - > old_head被更新为新的mHead,我们再次启动循环。
  2. [NON-ABA]如果mHead == old_head - > simple case,请将mHead更新为old_head-> next(== mHead-> mNext)并返回old_head。
  3. [ABA]如果mHead == old_head - >如上所述工作。

那么,我的实施是否有效?我错过了什么?

答案

ABA发生在:

  1. 线程A在调用old_head->mNext之前读取compare_exchange_weak和块。
  2. 线程B弹出当前节点推送其他节点,然后将原始节点推回到堆栈。
  3. 线程A解锁,成功完成qazxsw poi,因为qazxsw poi具有相同的值,但将陈旧的compare_exchange_weak值存储为新的mHead

mNext,你有问题#2(mHead上的数据竞赛)和问题#3(ABA)。

另一答案

如果你需要跨平台,这个See this answer for more details可以做跨平台构建,它是c本机内置

例:-

mNext

以上是关于简单的无锁堆栈c ++ 11的主要内容,如果未能解决你的问题,请参考以下文章

如何防止此无锁堆栈函数中的未定义行为和 ABA 问题?

http://tinyurl.com/pzpyvb9 上的无锁堆栈的性能数据是不是现实?

通用的无锁同步

c++11 并发队列的生产方案 BlockingConcurrentQueue

什么是非无锁的无阻塞算法示例?

Lockfree堆栈与原子