无锁堆栈可以实现为单链表。这看起来很简单,直到我们必须考虑在弹出节点后如何处理它们。一种策略是简单地将它们移动到每个堆栈的 LIFO 空闲列表(后续的推送操作可以重用该节点),直到最终所有线程都完成了堆栈,此时单个线程会销毁堆栈中的所有节点和所有节点。空闲列表中的节点。Boost.Lockfree使用这种策略。Chris Wellons 的 C11 实现也是如此。我将参考后者,因为它更容易阅读,而且细节基本相同,因为 C11 原子与 C++11 原子非常相似。
在 Wellons 的实现中(可以在 GitHub 上找到),所有lstack_node对象都是非原子的。特别是,这意味着对对象next成员的所有访问lstack_node都是非原子的。我无法理解的是:为什么此类访问从不相互竞争?
该成员在lstack.c:30 处next读取。它写在lstack.c:39。如果这两行可以在同一个对象上同时执行,则该程序包含竞争。这可能吗?对我来说似乎有可能:lstack_node
lstack_pop,它调用pop. 它以原子方式将头节点的值加载到局部变量中orig。现在,orig.node是指向刚刚位于堆栈顶部的节点的指针。(请注意,到目前为止,仅修改了局部变量,因此线程 1 迄今为止所做的任何操作都不可能导致任何其他线程中的 CAS 失败。)同时...lstack_pop. pop成功并返回node,指向刚刚从堆栈中删除的节点的指针;这与线程 1 中指向的节点相同。orig.node然后它开始调用push以添加node到空闲列表中。自由列表头节点以原子方式加载,并node->next设置为指向自由列表中的第一个节点。orig.node->next。难道 Wellons 的实施根本就是错误的吗?我对此表示怀疑。如果他的实现是错误的,那么 Boost …