C++ 0x中的Fences一般只保证原子或内存

edA*_*a-y 12 c++ multithreading memory-model memory-barriers c++11

的C++ 0x草案有围栏的概念似乎很明显,从围墙的CPU /芯片级的概念,或者说什么linux内核的家伙想到的围栏.问题在于草案是否真的意味着一个极其受限制的模型,或者措辞是否很差,它实际上意味着真正的围栏.

例如,在29.8 Fences下它表示如下:

如果存在原子操作X和Y,则释放围栏A与获取围栏B同步,两者都在某个原子对象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;
}
Run Code Online (Sandbox Code Playgroud)

假设a,b,c的大小在平台上具有原子拷贝.以上意味着c只会被分配9.注意我们并不关心线程B何时看到a==5,只是当它看到它时b==9.

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


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

不幸的是,这is_lock_free不是一个constexpr.这将允许它用于static_assert.有atomic<T>简并使用锁通常是一个坏主意:使用互斥体将具有比互斥设计的算法可怕的争夺问题原子的算法.

Ant*_*ams 15

栅栏提供订货的所有数据.但是,为了保证一个线程的fence操作对于第二个可见,您需要对该标志使用原子操作,否则您将进行数据竞争.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果thread_2读取readytrue,则围栏确保data可以安全地读取,输出将是data=42.如果ready被读取false,那么你不能保证thread_1已经发布了相应的fence,所以线程2中的fence仍然不会提供必要的排序保证 - 如果ifin thread_2被省略,则访问data将是数据竞争并且未定义行为,即使有围栏.

澄清:A 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在同一线程上“先于B”,并且B在另一线程上“与C”同步,并且C在第二个线程上“在D之前” D,则A *在D之前发生,并且A和D可以访问相同的数据,即使它们是非原子操作。给定的段落指定了B和C必须提供什么保证。如果非原子访问之间不存在“先于……”关系,那么您将进行数据竞争。 (2认同)