Multithreading C11/C++;11记忆模型获取、释放、放松细节

Multithreading C11/C++;11记忆模型获取、释放、放松细节,multithreading,c++11,c11,memory-barriers,abstract-machine,Multithreading,C++11,C11,Memory Barriers,Abstract Machine,我对C++11/C11内存模型有一些疑问,我想知道是否有人可以澄清。这些是关于模型/抽象机器的问题,而不是关于任何真正的架构 获取/释放效果是否保证从一个线程“级联”到下一个线程 下面是我的意思的伪代码示例(假设所有变量都以0开头) 线程3的acquire与线程2的release同步,这发生在线程2的acquire与线程1的release同步之后。因此,线程3保证看到线程1设置为x的值,对吗?或者我们需要在这里使用seq cst来保证断言不会触发吗?我觉得获得/释放就足够了,但我找不到任何简单

我对C++11/C11内存模型有一些疑问,我想知道是否有人可以澄清。这些是关于模型/抽象机器的问题,而不是关于任何真正的架构


  • 获取/释放效果是否保证从一个线程“级联”到下一个线程
  • 下面是我的意思的伪代码示例(假设所有变量都以0开头)

    线程3的acquire与线程2的release同步,这发生在线程2的acquire与线程1的release同步之后。因此,线程3保证看到线程1设置为x的值,对吗?或者我们需要在这里使用seq cst来保证断言不会触发吗?我觉得获得/释放就足够了,但我找不到任何简单的解释来保证这一点。大多数关于获取/发布的解释主要集中在获取线程接收发布线程所做的所有存储。但是,在上面的示例中,线程2从不接触变量x,线程1/线程3不接触同一个原子变量。很明显,如果线程2要加载x,它会看到1,但是该状态是否保证会级联到其他线程中,这些线程随后会与线程2进行获取/释放同步?或者线程3是否也需要对变量a执行一次获取以接收线程1对x的写入

    根据:

    在获取相同原子变量的其他线程中,当前线程中的所有写操作都是可见的

    释放相同原子变量的其他线程中的所有写操作在当前线程中可见

    由于线程1和线程3不接触同一个原子变量,我不确定仅获取/释放就足以满足上述情况。在正式描述中可能隐藏着一个答案,但我不能完全理解

    *编辑:直到事后才注意到,但我发布的链接中有一个示例(“下面的示例演示了可传递的发布获取顺序…”)与我的示例几乎相同,但它在所有三个线程中使用相同的原子变量,这似乎很重要。我特别问的是变量不相同的情况


  • 我认为,根据标准,必须始终存在一对非松弛原子操作,每个线程中一个,才能保证任何类型的内存顺序,这是对的吗
  • 假设有一个函数“get_data”,它分配一个缓冲区,向其中写入一些数据,并返回一个指向缓冲区的指针。还有一个函数“use_data”,它获取指向缓冲区的指针,并对数据进行处理。线程1从get_数据中获取一个缓冲区,并使用一个到全局原子指针的放松原子存储将其传递给线程2。线程2在循环中执行松弛的原子加载,直到获得指针,然后将其传递给使用_数据:

    int* get_data() {...}
    void use_data(int* buf) {...}
    int* global_ptr = nullptr;
    
    [Thread 1]
    int* buf = get_data();
    super_duper_memory_fence();
    store_relaxed(global_ptr, buf);
    
    [Thread 2]
    int* buf = nullptr;
    while ((buf = load_relaxed(global_ptr)) == nullptr);
    use_data(buf);
    
    是否有任何类型的操作可以放在“超级复制器内存围栏”中,以保证在use\u data获得指针时,缓冲区中的数据也可见?我的理解是,没有一种可移植的方法可以做到这一点,线程2必须有一个匹配的围栏或其他原子操作,以确保它接收写入缓冲区的写入,而不仅仅是指针值。这是正确的吗

    线程3的acquire与线程2的release同步,这发生在线程2的acquire与线程1的release同步之后。因此,线程3保证看到线程1设置为x的值,对吗

    是的,这是正确的。获取/释放操作建立与同步的关系,即,
    store\u release(a)
    load\u获取(a)
    同步,
    store\u release(b)
    load\u acquire(b)
    同步。而
    load\u acquire(a)
    store\u release(b)
    之前被排序。synchronize with和sequenced before都是“发生在之前”定义的一部分,并且“发生在之前”关系是可传递的。因此,
    store\u released(x,1)
    发生在
    load\u released(x)
    之前

    我认为,根据标准,必须始终存在一对非松弛原子操作,每个线程中一个,才能保证任何类型的内存顺序,这是对的吗

    这个问题有点过于宽泛,但总的来说,我倾向于说“是”。通常,在对某些(非原子的)共享数据进行操作时,必须确保存在适当的“发生在前”关系。如果一个线程写入某个共享数据,而另一个线程应该读取该数据,则必须确保写入发生在读取之前。实现这一点有不同的方法——具有正确内存顺序的原子只是一种方法(尽管有人可能认为几乎所有其他方法(如
    std::mutex
    )也可以归结为原子操作)

    围栏还必须与其他围栏或原子操作相结合。如果
    super\u duper\u memory\u fence()
    是一个
    std::atomic\u thread\u fence(std::memory\u order\u release)
    并且在调用
    use\u data
    之前放置另一个
    std::atomic\u thread\u fence(std::memory\u order\u acquire)
    ,则您的示例将起作用

    关于更多细节,我可以推荐我与他人合著的这篇论文:

    线程3的acquire与线程2的release同步,这发生在线程2的acquire与线程1的release同步之后。因此,线程3保证看到线程1设置为x的值,对吗

    是的,这是正确的。获取/释放操作建立与同步的关系,即,
    store\u release(a)
    load\u获取(a)
    同步,
    store\u release(b)
    load\u acquire(b)
    同步。而
    load\u acquire(a)
    store\u release(b)
    之前被排序。与b同步并按顺序排列
    int* get_data() {...}
    void use_data(int* buf) {...}
    int* global_ptr = nullptr;
    
    [Thread 1]
    int* buf = get_data();
    super_duper_memory_fence();
    store_relaxed(global_ptr, buf);
    
    [Thread 2]
    int* buf = nullptr;
    while ((buf = load_relaxed(global_ptr)) == nullptr);
    use_data(buf);