首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >关于C++11无锁堆栈push()函数的混淆

关于C++11无锁堆栈push()函数的混淆
EN

Stack Overflow用户
提问于 2016-12-13 09:00:01
回答 1查看 196关注 0票数 4

我正在阅读安东尼·威廉姆斯的“在行动中的C++并发”,并且不理解它对lock_free_stack类的推送实现。清单7.12准确地说

代码语言:javascript
复制
void push(T const& data)
{
    counted_node_ptr new_node;
    new_node.ptr=new node(data);
    new_node.external_count=1;
    new_node.ptr->next=head.load(std::memory_order_relaxed) 
    while(!head.compare_exchange_weak(new_node.ptr->next,new_node, std::memory_order_release, std::memory_order_relaxed));
}

因此,假设有两个线程(AB)调用push函数。它们都在循环时到达,但没有启动。因此,他们都从head.load(std::memory_order_relaxed)读取相同的值。

接下来我们要做的事情如下:

  1. B线程因任何原因被删除。
  2. A线程启动循环,显然成功地将一个新节点添加到堆栈中。
  3. B线程回到正轨,并启动循环。

在我看来,这就是有趣的地方。因为在成功的情况下,有一个带有、load、compare_exchange_weak(..., std::memory_order_release, ...)std::memory_order_relaxed加载操作,所以线程之间似乎没有同步。我是说,这就像std::memory_order_relaxed - std::memory_order_release ,而不是 std::memory_order_acquire - std::memory_order_release

因此,B线程将简单地将一个新节点添加到堆栈中,但在堆栈中没有节点时将其添加到它的初始状态,并将头重置到这个新节点。

我一直在做关于这个问题的研究,我所能找到的最好的就是在这篇exchange按修改顺序读取最后值?上。

所以问题是,这是真的吗?所有的RMW函数都以修改的顺序看到最后的值?无论我们使用什么std::memory_order,如果我们使用RMW操作,它将与所有线程(CPU等)同步,并找到被写入原子操作的最后一个值被调用?

EN

回答 1

Stack Overflow用户

发布于 2017-01-13 08:46:56

因此,经过一些研究,并问了一群人,我相信我找到了正确的答案,这个问题,我希望这将是一个帮助的人。

所以问题是,这是真的吗?所有的RMW函数都以修改的顺序看到最后的值?

是的,这是真的。

无论我们使用什么std::memory_order,如果我们使用RMW操作,它将与所有线程(CPU等)同步,并找到被写入原子操作的最后一个值被调用?

是的,这也是事实,但是有一些事情需要强调。

RMW运行将只同步一起工作的原子变量。在我们的例子中,它是head.load

也许您会问,如果RMW完成了同步,那么我们为什么需要发布-获取语义,即使是在轻松的内存顺序下。

答案是因为RMW只与它同步的变量一起工作,但是在RMW之前发生的其他操作可能在另一个线程中看不到。

让我们再看一下push函数:

代码语言:javascript
复制
void push(T const& data)
{
    counted_node_ptr new_node;
    new_node.ptr=new node(data);
    new_node.external_count=1;
    new_node.ptr->next=head.load(std::memory_order_relaxed) 
    while(!head.compare_exchange_weak(new_node.ptr->next,new_node, std::memory_order_release, std::memory_order_relaxed));
}

在这个例子中,如果使用两个推送线程,它们在某种程度上不会被同步,但在这里可以允许。

两个线程都会看到最新的头,因为compare_exchange_weak提供了这个。并且一个新的节点将始终添加到堆栈的顶部。但是,如果我们试图在这行之后获得类似于这个*(new_node.ptr->next)的值,那么new_node.ptr->next=head.load(std::memory_order_relaxed)就很容易变得丑陋:空指针可能被取消引用的。这可能是因为处理器可以改变指令的顺序,而且由于线程之间没有同步,第二个线程甚至在初始化前就可以看到指向顶级节点的指针!

这正是发布-获取语义来帮助的地方。它保证了在发布操作之前发生的所有操作都会出现在获取部分!查看并比较书中的清单5.5和5.8。

我还建议您阅读这篇关于处理器如何工作的文章,它可能为更好地理解提供一些基本信息。记忆屏障

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41117053

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档