简单的无锁堆栈c ++ 11

时间:2014-11-04 23:48:39

标签: c++ multithreading c++11 stack lock-free

我已经看到了几个过于复杂的(在我看来很明显)在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私有方法,并使用新的公共函数来处理内存分配并在内部调用这些函数。我相信这个实现中最棘手的部分是我用&#34; CL1&#34;标记的行。我认为它的正确性仍然适用于ABA案例的原因如下:

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

总结:

  1. 如果mHead!= old_head(另一个帖子先发制人并更改了mHead) - &gt; old_head被更新为新的mHead,我们再次开始循环。
  2. [NON-ABA]如果mHead == old_head - &gt;简单的情况,将mHead更新为old_head-&gt; next(== mHead-&gt; mNext)并返回old_head。
  3. [ABA]如果mHead == old_head - &gt;如上所述。
  4. 那么,我的实施是否有效?我错过了什么?

3 个答案:

答案 0 :(得分:5)

ABA发生在:

  1. 线程A在调用old_head->mNext之前读取compare_exchange_weak并阻止。
  2. 线程B弹出当前节点推送其他节点,然后将原始节点推回堆栈。
  3. 线程A取消阻止,成功完成compare_exchange_weak,因为mHead具有相同的值,但将陈旧的mNext值存储为新的mHead
  4. See this answer for more details,你有问题#2(mNext上的数据竞赛)和问题#3(ABA)。

答案 1 :(得分:1)

一般来说,如果数据类型是按位正则的,那么实现可能是正确的,如果只是半正则(或相等不是按位),则不可避免地会受到 ABA 的影响。

如果数据类型是按位规则的,则 CAS 操作就足够了,例如对于整数。 ABA 不是问题,因为 A 等于 A,故事结束。

Harris 算法似乎允许非按位正则类型提供集合实现,但以无界性能为代价,更糟糕的是,它引出了如何分配列表节点的问题。这意味着要解决的核心问题是提供一个 O(1) 无锁分配器,而这样的数据结构是不存在的,因为所涉及的指针只是半规则的。特别是,当指针仅将字节数组标识为内存块时,它是规则的,当它标识链表的节点时,它不再是规则的,因为指针相等并不能确保嵌入的下一个指针相等。

没有可能的解决方法。我怀疑 DCAS 会有所帮助,我怀疑将恒定时间限制放宽到线性是否也可以。

对于不可抢占的线程,只要它保护的临界区是有界时间,自旋锁就可以工作。您可以在 MacOS 和 RT-Linux 上获得这些。

答案 2 :(得分:0)

如果您需要跨平台,则此lfstack可以跨平台构建,它是内置于

的c语言。

示例:-

int* int_data;
lfstack_t mystack;

if (lfstack_init(&mystack) == -1)
    return -1;

/** Wrap This scope in other threads **/
int_data = (int*) malloc(sizeof(int));
assert(int_data != NULL);
*int_data = i++;
/*PUSH*/
 while (lfstack_push(&mystack, int_data) == -1) {
    printf("ENQ Full ?\n");
}

/** Wrap This scope in other threads **/
/*POP*/
while  ( (int_data = lfstack_pop(&mystack)) == NULL) {
    printf("POP EMPTY ..\n");
}

// printf("%d\n", *(int*) int_data );
free(int_data);
/** End **/

lfstack_destroy(&mystack);