【问题标题】:Is there any effect on the operations with the variables independent of consume atomic-load?独立于消耗原子负载的变量对操作有影响吗?
【发布时间】:2016-07-30 12:11:42
【问题描述】:

众所周知,std::memory_order's 有 6 个,其中有 2 个:

  • acquire-semantic 用于加载 - 避免重新排序 Load-Load 和 Load-Store
  • 用于商店的发布语义 - 避免重新排序 Store-Store 和 Load-Store

即对于acquire-semantic,只有S = local1;可以在X.load(std::memory_order_acquire);之后执行:

static std::atomic<int> X;
static int L, S;
...

void thread_func() 
{
    int local1 = L;  // load(L)-load(X) - !!! can be reordered with X !!!
    S = local1;      // store(S)-load(X) - !!! can be reordered with X !!!

    int x_local = X.load(std::memory_order_acquire);  // load(X)

    int local2 = L;  // load(X)-load(L) - can't be reordered with X
    S = local2;      // load(X)-store(S) - can't be reordered with X
}

但是load(X) 中的哪些重新排序可以用于消费语义?

static std::atomic<int *> X;
static int L1, L2, S1, S2;
static int L, S;
...

void thread_func() 
{
    int *x_ptr_local = new int(1);


    int local1 = L1;  // load(L1)-load(X) - !!! can be reordered with X !!!
    S1 = local1;      // store(S1)-load(X) - !!! can be reordered with X !!!

    int dependent_x1 = *x_ptr_local;  // load(x_ptr)-load(X) - !!! can be reordered with X !!!
    S = dependent_x1;                 // store(S)-load(X) - !!! can be reordered with X !!!

    x_ptr_local = X.load(std::memory_order_consume);  // load(X)

    int dependent_x2 = *x_ptr_local;  // load(X)-load(x_ptr) - can't be reordered with X
    S = dependent_x2;                 // load(X)-store(S) - can't be reordered with X

    int local2 = L2;  // load(X)-load(L2) - !!! can be reordered with X !!!
    S2 = local2;      // load(X)-store(S2) - !!! can be reordered with X !!!
}

真的吗,只有dependent_x2 的操作不能在X.load(std::memory_order_consume) 中重新排序?

所有带有变量L1L2S1S2dependent_x1 的操作都可以在X.load(std::memory_order_consume) 中重新排序 - 即可以在X.load(std::memory_order_consume) 之前或之后执行,不是是吗?

【问题讨论】:

  • 为什么不能在第一个例子中将int local1 = L; 重新排序到x_local 之下?
  • @2501 (1) int local1 = L; 不能在 (2) int x_local = X.load(std::memory_order_acquire); 下面重新排序,因为两者都是 - 负载和std::memory_order_acquire 防止 Load-Load 重新排序 - 如图所示,不是吗?
  • 我只看到 2 具有获取语义,所以就像S = local1; 可以低于 21 也可以乙>。 1有什么特别之处?
  • @2501 谢谢!我修复了它,如下所述:stackoverflow.com/questions/38677061/…
  • @Alex,你的第二个例子中的X 不应该是std::atomic&lt;int*&gt; 而不是std::atomic&lt;int&gt;*

标签: c++ multithreading c++11 concurrency stdatomic


【解决方案1】:

memory_order_consume 用于保留对原子对象本身的数据依赖关系的顺序,而不使用像memory_order_acquire 引入的更重的同步。对于memory_order_acquire,在acquire 之后的所有 内存操作——取决于原子变量或其他——被禁止在它之前重新排序,而memory_order_consume 只禁止重新排序依赖指令。这对于诸如 ARM 和 PowerPC 等更弱排序的体系结构是有益的,它们保证了数据相关指令的排序,而无需显式屏障。

由于memory_order_consume 处理数据依赖关系,它的大多数用例都涉及std::atomic&lt;T*&gt;。生产者线程可以构建一个完整的数据结构,并使用memory_order_release将该数据结构的地址发布到原子指针。消费者线程然后使用memory_order_consume 加载原子指针,并且如果它们以数据依赖的方式使用指针,例如通过取消引用它,则可以与写入线程的存储建立数据依赖关系。该标准保证任何依赖加载都将反映编写器线程的存储。但是,由于原子变量的加载是通过memory_order_consume 完成的,因此从阅读线程的角度来看,不能保证自变量的状态。

在您的第一个示例中,memory_order_acquire 之后的所有负载都不能在它之前重新排序。但是,在您的第二个示例中,任何不依赖于 X 或其加载值的重新排序都是公平的游戏。也就是说,int dependent_x2 = *x_ptr_local;(以及来自dependent_x2 的相应负载)保证相对于X 保持有序,仅此而已。所有其他重新排序都是可能的。

【讨论】:

  • 谢谢!您还写了关于“数据相关的方式,例如通过取消引用它”,即std::memory_order_consume 使指针指向的整个分配的内存区域具有消费语义,即使消费指针指向数组的中间,那么整个数组会是可见的吗?它会起作用吗? int arr[1000000]; std::atomic&lt;int*&gt; ptr; thread-1: arr[0] = 255; arr[999999] = 127; ptr.store(arr+500000, std::memory_order_release); thread-2: int *p; while (!(p = ptr.load(std::memory_order_consume))); std::cout &lt;&lt; *(p) &lt;&lt; ", " &lt;&lt; *(p + 499999);
  • @Alex,我会澄清我的答案,但是因为您使用 ptr 的加载值来计算数组的偏移量,所以它与将其用作取消引用基本相同,并且数据依赖成立。但是请注意,如果您改为尝试 assert(arr[999999] == 127),则不会保证为真,因为您没有在读取端 ptr 和在生产方。
  • 你的意思是 thread-2: asser(p[999999] == 127) 会失败吗?但这是 UB,因为 thread-2 中的 p 相对于 thread-1 的指针 arr 移动了 +500000 - 它超出了边界。如果你的意思是 thread-2: asser(p[499999] == 127) 会失败,那为什么会发生呢?在发布消费排序的例子中,void consumer() { std::string* p2; while (!(p2 = ptr.load(std::memory_order_consume))); assert(p2[4] == 'o'); 它永远不会是假的:en.cppreference.com/w/cpp/atomic/memory_order
  • @Alex,我的意思是 thread-2 中的 arr[999999],因此它是 not 超出数组边界的未定义行为。它可能会失败,因为arr 不依赖于p。因此,来自arr[999999] 的加载可以在来自p 的加载之前重新排序。但是,如果您要在 thread-2assert(p[499999] == 127) 中进行操作,您将保持对 p 的依赖。
  • 我同意。谢谢你的澄清。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-13
  • 2023-03-30
  • 2018-02-18
  • 2019-07-23
  • 2018-01-06
相关资源
最近更新 更多