在C ++ 0x中设置栅栏,一般可以保证原子或内存

C ++ 0x草案有一个围栏的概念,它似乎与围墙的CPU /芯片级别概念截然不同,或者说出Linux内核人员对围墙的期望。 问题在于草案是否真的意味着一个非常有限的模式,或者措辞只是很差,实际上意味着真正的围墙。

例如,根据29.8 Fences,它会说明如下内容:

如果存在对原子对象M进行操作的原子操作X和Y,则释放栅栏A与获取栅栏B同步,使得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中的代码保证了相同的关系?


回答 :如果你阅读我选择的答案和所有评论,你将会得到这个情况的要点。 C ++ 0x似乎强迫你使用带有栅栏的原子,而普通的硬件栅栏不具备这个要求。 在很多情况下,只要sizeof(atomic<T>) == sizeof(T)atomic<T>.is_lock_free() == true这仍然可以用来替换并发算法。

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


栅栏提供所有数据的 排序 。 但是,为了保证一个线程的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;
    }
}

如果thread_2读取readytrue ,那么这些fence确保可以安全地读取data ,并且输出将是data=42 。 如果ready被读取为false ,那么您不能保证thread_1已经发布了适当的fence,因此thread 2中的fence仍然不会提供必要的排序保证---如果省略thread_2中的if ,则访问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.无用的,2.在当前硬件上找不到。

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

WRT。 原子对象,我的理解是在Linux支持的所有目标上,它们的sizeof()<= sizeof(* void)是原子的正确对齐的纯整数变量,因此Linux使用普通整数作为同步变量(即Linux内核原子操作在正常的整数变量上)。 C ++不想施加这样的限制,因此是独立的原子整数类型。 此外,在C ++中,对原子整数类型的操作意味着障碍,而在Linux内核中,所有障碍都是显式的(这是显而易见的,因为没有编译器支持原子类型,这是必须做的)。

链接地址: http://www.djcxy.com/p/30717.html

上一篇: Fences in C++0x, guarantees just on atomics or memory in general

下一篇: Compare and swap C++0x