【问题标题】:Fences in C++0x, guarantees just on atomics or memory in generalC++0x 中的栅栏,一般只保证原子或内存
【发布时间】:2011-04-05 04:29:54
【问题描述】:

C++0x draft 有一个栅栏的概念,这似乎与 CPU/芯片级别的栅栏概念非常不同,或者说一下 linux 内核人员对 fences 的期望。问题是草案是否真的暗示了一个极其受限的模型,或者措辞很差,实际上暗示了真正的围栏。

例如,在 29.8 Fences 下,它声明如下:

释放栅栏 A 与 如果存在 atomic 则获取栅栏 B 操作 X 和 Y,都在 一些原子对象 M,使得 A 是 在 X 之前排序,X 修改了 M,Y 是 在 B 之前排序,Y 读取 X写入的值或写入的值 通过假设中的任何副作用 如果发布序列 X 是一个释放操作。

它使用这些术语atomic operationsatomic object。草案中定义了这样的原子操作和方法,但是否仅指这些? 发布围栏听起来像商店围栏。不保证在栅栏之前写入所有数据存储栅栏几乎是无用的。加载(获取)栅栏和完整栅栏类似。

那么,C++0x 中的栅栏/栅栏是不是适当的栅栏和措辞非常糟糕,或者它们是否像描述的那样受到严格限制/无用?


就 C++ 而言,假设我有这个现有代码(假设栅栏现在可以作为高级构造使用——而不是说在 GCC 中使用 __sync_synchronize):

Thread A:
b = 9;
store_fence();
a = 5;

Thread B:
if( a == 5 )
{
  load_fence();
  c = b;
}

假设 a,b,c 的大小可以在平台上进行原子复制。以上意味着c 将永远被分配9。请注意,我们并不关心线程 B 何时看到 a==5,只是在它看到时它也会看到 b==9

C++0x中保证相同关系的代码是什么?


ANSWER:如果您阅读我选择的答案和所有 cmets,您将了解情况的要点。 C++0x 似乎迫使您使用带栅栏的原子,而普通的硬件栅栏没有这个要求。在许多情况下,只要sizeof(atomic<T>) == sizeof(T)atomic<T>.is_lock_free() == true,它仍然可以用来替换并发算法。

不幸的是,is_lock_free 不是 constexpr。这将允许它在static_assert 中使用。让atomic<T> 退化为使用锁通常是个坏主意:与使用互斥锁设计的算法相比,使用互斥锁的原子算法将存在可怕的争用问题。

【问题讨论】:

  • 贫化铀已被取代为炮弹的首选材料。 C++0x 草案的过时副本被证明更加密集。
  • 虽然只有一个月左右。
  • 汉斯可能知道一些我们不知道的事情? C++11 真正的提案有望在下周发布。
  • 为什么 ff 连字实际上是这样呈现的?
  • 在规范中提及“原子操作”,无论是标准还是其他,只能引用该文档中描述为“原子操作”的那些操作,而不是其他任何没有的操作碰巧被视为完成了一半。

标签: c++ multithreading c++11 memory-barriers memory-model


【解决方案1】:

栅栏对所有数据提供排序。但是,为了保证一个线程的栅栏操作对第二个线程可见,需要对标志使用原子操作,否则会出现数据竞争。

std::atomic<bool> ready(false);
int data=0;

void thread_1()
{
    data=42;
    std::atomic_thread_fence(std::memory_order_release);
    ready.store(true,std::memory_order_relaxed);
}

void thread_2()
{
    if(ready.load(std::memory_order_relaxed))
    {
        std::atomic_thread_fence(std::memory_order_acquire);
        std::cout<<"data="<<data<<std::endl;
    }
}

如果thread_2ready 读取为true,则围栏确保可以安全地读取data,并且输出将为data=42。如果ready 被读取为false,那么你不能保证thread_1 已经发布了适当的栅栏,所以线程2 中的栅栏仍然不能提供必要的排序保证——如果@ 中的if 987654331@ 被省略,对data 的访问将是数据竞争和未定义的行为,即使有栅栏。

澄清:std::atomic_thread_fence(std::memory_order_release) 通常相当于商店围栏,并且很可能会这样实现。但是,一个处理器上的单个栅栏并不能保证任何内存排序:您需要在第二个处理器上设置一个相应的栅栏,并且您需要知道执行获取栅栏时释放栅栏的效果对第二个处理器可见。很明显,如果 CPU A 发出了一个获取围栏,然后 5 秒后 CPU B 发出了一个释放围栏,那么这个释放围栏就无法与获取围栏同步。除非您有某种方法可以检查是否在其他 CPU 上发出了栅栏,否则 CPU A 上的代码无法判断它是在 CPU B 上的栅栏之前还是之后发出了栅栏。

您使用原子操作来检查是否已看到栅栏的要求是数据竞争规则的结果:您不能在没有顺序关系的情况下从多个线程访问非原子变量,因此您不能使用用于检查排序关系的非原子变量。

当然可以使用更强大的机制,例如互斥锁,但这会使单独的围栏变得毫无意义,因为互斥锁会提供围栏。

宽松的原子操作可能只是现代 CPU 上的简单加载和存储,尽管可能需要额外的对齐要求以确保原子性。

如果用于检查同步的操作(而不是用于访问同步数据的操作)是原子的,则为使用特定于处理器的栅栏而编写的代码可以很容易地更改为使用 C++0x 栅栏。现有代码很可能依赖于给定 CPU 上的普通加载和存储的原子性,但转换为 C++0x 将需要对这些检查使用原子操作以提供排序保证。

【讨论】:

  • 标准的哪一部分阐明了排序是基于 所有数据 而不仅仅是原子。这就是我要找的东西?您必须使用原子(如您所指出的)的要求意味着这些不是适当的围栏。
  • 标准的相关章节是1.10、29.3和29.8。排序规则比较复杂,但是如果A是sequenced-before B在同一个线程,Bsynchronizes-with C在另一个线程,C是sequenced -before D 在第二个线程上,然后 A happens-before D,并且 A 和 D 可以访问相同的数据,即使它们是非原子操作。给定的段落指定了 B 和 C 必须是什么才能提供此保证。如果非原子访问之间没有 happens-before 关系,那么您就有了数据竞争。
  • 见我的澄清:现有的硬件围栏通常确实需要一个原子操作来创建 happens-before 关系,它只是碰巧普通加载和寄存器大小的适当对齐数据的存储在现代 CPU 上是原子的。
  • 在对齐 int 的加载和存储是原子的平台上(例如 x86 linux),std::atomic&lt;int&gt; 很可能具有与 int 相同的大小和对齐方式。在我的实现中确实如此。在int 的加载和存储不是原子的平台上,std::atomic&lt;int&gt; 将做任何必要的事情来保证访问的原子性(即使那是使用互斥锁)。同样,std::thread_fence() 将产生任何必要的指令来提供适当的排序保证 --- 很可能是一个硬件栅栏指令(甚至是一个 nop,例如 x86 获取栅栏)
  • 您的代码示例依赖于对 a 的存储和加载是原子的。你有一个不是这种情况的例子吗?
【解决方案2】:

我的理解是它们是适当的围栏。间接证据是,毕竟,它们旨在映射到实际硬件中发现的特性,并允许有效地实现同步算法。正如您所说,仅适用于某些特定值的围栏是 1. 无用和 2. 在当前硬件上找不到。

话虽如此,您引用的 AFAICS 部分描述了栅栏和原子操作之间的“同步”关系。有关这意味着什么的定义,请参阅第 1.10 节多线程执行和数据竞争。同样,AFAICS,这并不意味着栅栏仅适用于原子对象,而是我怀疑其含义是,虽然普通加载和存储可能以通常的方式(仅一个方向)通过获取和释放栅栏,但原子加载/商店可能不会。

写。原子对象,我的理解是,在 Linux 支持的所有目标上,正确对齐的纯整数变量 sizeof()

【讨论】:

  • 我重读了第 1.10 节,我完全不清楚排序保证是否适用于非原子值。几乎所有的点都只表示atomic objects。第 13 点提到了非标量,前面的项目与关系有关,但我不肯定这真的说栅栏可以作为硬件栅栏。
  • 在汇编级别,对普通int 变量的加载或存储在Linux 支持的所有CPU 上都是原子的(这样的对象总是根据ABI 对齐)。但是在更高级别的 C/C++ 级别,数据竞赛具有 UB。编译器不希望正常变量被另一个执行(其他线程或异步信号处理程序)更改。 (只有 volatile 对象才能通过源代码中不可见的方式进行修改。)如果没有可见代码可以在函数中更改对象,编译器可以假定对象保留其先前的值。
猜你喜欢
  • 2011-04-20
  • 2017-11-25
  • 1970-01-01
  • 2010-10-06
  • 2021-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多