Multithreading 关于C+的困惑+;11无锁堆栈push()函数 我在Anthony Williams的操作中读取C++并发,不理解它的代码实现: LoopyFuffStase类。准确地说,清单7.12 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)); }

Multithreading 关于C+的困惑+;11无锁堆栈push()函数 我在Anthony Williams的操作中读取C++并发,不理解它的代码实现: LoopyFuffStase类。准确地说,清单7.12 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)); },multithreading,c++11,concurrency,Multithreading,C++11,Concurrency,因此,想象两个线程(A,B)调用push函数。他们都到达while循环,但没有启动它。因此,它们都从head.load(std::memory\u order\u released)读取相同的值 然后,我们将进行以下工作: B线程因任何原因被刷出 一个线程启动循环,显然成功地将一个新节点添加到堆栈中 B线程回到正轨并启动循环 这就是我觉得有趣的地方。 因为在std::memory\u order\u released和compare\u exchange\u弱(…,std::memory\u o

因此,想象两个线程(
A
B
)调用push函数。他们都到达while循环,但没有启动它。因此,它们都从
head.load(std::memory\u order\u released)
读取相同的值

然后,我们将进行以下工作:

  • B
    线程因任何原因被刷出
  • 一个
    线程启动循环,显然成功地将一个新节点添加到堆栈中
  • B
    线程回到正轨并启动循环
  • 这就是我觉得有趣的地方。 因为在
    std::memory\u order\u released
    compare\u exchange\u弱(…,std::memory\u order\u release,…)
    的情况下有一个load操作,如果成功,线程之间似乎没有同步。 我的意思是,这就像
    std::memory\u order\u release-std::memory\u order\u release
    而不是
    std::memory\u order\u acquire-std::memory\u order\u release

    因此
    B
    线程只需将一个新节点添加到堆栈中,但当堆栈中没有节点时,它将恢复到初始状态,并将头部重置到这个新节点

    我一直在围绕这一主题进行研究,我能找到的最好的答案就是这篇文章


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

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

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

    是的,这是真的

    无论我们使用什么std::memory\u顺序,如果我们使用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\u exchange\u弱
    提供了这一点。新节点将始终添加到堆栈的顶部。 然而,如果我们试图在这一行之后获得这样的值
    *(new\u node.ptr->next)
    new\u node.ptr->next=head.load(std::memory\u order\u released)
    事情很容易变得糟糕:空指针可能被取消引用。 这可能是因为处理器可以更改指令顺序,而且线程之间没有同步,所以第二个线程甚至在初始化之前就可以看到指向顶部节点的指针

    这正是release acquire semantic发挥作用的地方。它确保在发布操作之前发生的所有操作都可以在获取部分中看到! 查看并比较本书中的清单5.5和5.8

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