我已经看到了几个过于复杂的(在我看来很明显)在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仍然是正确的!
总结:
那么,我的实施是否有效?我错过了什么?
答案 0 :(得分:5)
ABA发生在:
old_head->mNext
之前读取compare_exchange_weak
并阻止。compare_exchange_weak
,因为mHead
具有相同的值,但将陈旧的mNext
值存储为新的mHead
。 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);