混淆C ++ 11锁定自由堆栈push()函数

时间:2016-12-13 09:00:01

标签: multithreading c++11 concurrency

我正在阅读Anthony Williams的C ++ Concurrency in Action,并且不理解它对var global = { homepage: '/list/', urlRedirect: function (url) { window.location.href = url; }, xButtons: this.homepage, url: this.urlRedirect('items') }; console.log(global.xButtons); // gives me undefined console.log(global.homepage); // gives /list/ global.url // urlRedirect is not a function 类的推送实现。清单7.12准确

lock_free_stack

因此想象两个线程(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)); } A)调用push函数。它们都在循环中到达但不能启动它。所以他们都从B读取相同的值。

然后我们会发生以下事情:

  1. head.load(std::memory_order_relaxed)线程因任何原因被删除
  2. B线程启动循环,显然成功地向堆栈添加了一个新节点。
  3. A线程重回正轨并开始循环。
  4. 这就是我觉得有趣的地方。 因为有Bstd::memory_order_relaxed加载操作,如果成功,看起来线程之间没有任何同步。 我的意思是它就像compare_exchange_weak(..., std::memory_order_release, ...) 而不是 std::memory_order_relaxed - std::memory_order_release

    所以std::memory_order_acquire - std::memory_order_release线程只会向堆栈中添加一个新节点,但是当我们在堆栈中没有节点并重置到这个新节点时,它会进入初始状态。

    我正在围绕这个主题进行研究,我能找到的最好的就是这篇文章Does exchange or compare_and_exchange reads last value in modification order?

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

1 个答案:

答案 0 :(得分:0)

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

  

所以问题是,这是真的吗?所有RMW功能都看到了最后一个   修改顺序中的值?

是的,这是真的。

  

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

是的,这也是事实,但有些事情需要突出显示。

RMW操作将同步仅与其一起使用的原子变量。在我们的例子中,它是 head.load

也许您想问为什么我们需要发布 - 如果RMW即使使用宽松的内存顺序进行同步也会获得语义。

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

让我们再次看一下推送功能:

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。

我还建议您阅读有关处理器如何工作的文章,它可能会提供一些基本信息以便更好地理解。 memory barriers