2012-09-17 17 views
3

比方说,我有以下代码:C++内存排序一致性

void* p0 = nullptr; 
void* p1 = alloc_some_data(); 
void f1() { 
    p0 = p1; 
    p1 = nullptr; 
} 

假设f1上线1.运行是否有可能(离开代码,因为它是)另一个线程可能在某一时刻看到p0p1作为nullptr(如果编译器或硬件重新排序指令,比如第二次分配在第一次之前发生)?

我问这个的原因是因为我想实现一个垃圾收集器,我想知道是否需要使用原子指令访问GC线程中的指针(std::atomic)。如果GC线程看到p0 == p1 == alloc_some_data()则没有问题,但如果GC线程看到p0 == p1 == nullptr,则会出现问题,因为它会将p1中以前的数据报告为无法访问,因为它显然是可访问的。

回答

0

是的。虽然不一定可能,但完全有可能,因为这些操作不是原子的。

一(几可能的情况)是这样的:

Thread 2: Get value of p0 (null) 

Thread 1: Get value of p1 (non-null) 
      p0 = p1 
      p1 = nullptr 

Thread 2: Get value of p1 (null) 

您需要使用某种形式的访问控制(互斥)的。

+0

从我的理解来看,跟踪gc over ref counting的好处是避免锁(以refs为增量)。如果每个更新程序线程中的引用更新需要互斥锁,那么性能增益优先于ref计数?跟踪gc方法然后像lock参数计数方法一样使用锁膨胀代码路径(在mutator线程中),对吧?是否可以编写一个跟踪gc,其中的增变器线程可以用一个简单的MOV指令更新一个引用?任何人都可以指向一个方向(一篇文章或一本书...)吗? –

+0

我不太了解垃圾回收评论。就我个人而言,我喜欢引用计数,因为内存尽快释放。某些操作系统提供的原子增量和减量指令比使用互斥锁要快得多。在这种情况下,引用计数会胜出。如果您使用的是Windows/Visual Studio,则可能需要查看Microsoft特定的“volatile”语义。 – paddy

+1

@DaniloCarvalho:我对GC也不太熟悉,但很多非引用计数GC都是* stop-the-world * GC:环境将停止所有线程,执行GC(可能重定位对象和更新指针),然后让所有线程继续。 –

2

如果您在一个线程中读取了一个由另一个线程写入而没有同步的对象,那么您将有一个数据竞争。这显然意味着您的垃圾回收器需要使用某种同步来读取值。关于您的原始问题:您的代码中没有任何内容表示在写入p1之前,对p0的写入变得可见,即另一个线程的确可以看到两者都为空。这与用于与另一个线程通信的同步原语无关:在这两个写入之间没有排序。

0

你的问题的答案是肯定的,但它是编译器和CPU的依赖。我认为你还需要使p0p1不稳定。要停止重新排序,你可以使用_mm_sfence_mm_lfence instrinsics(用于x86/x64)

+1

制作对象'volatile'与C++ 2011中的线程完全无关。此外,C++ 2011还引入了标准化的函数来引入栅栏(不过,由于我没有尝试过使用它们,所以我不能评论如何使用它们)。 –

+0

@DietmarKühl你有链接支持缺乏挥发的需要吗?编译器如何知道不要缓存读取? – James

+0

据我所知,当硬件/中断可以改变内存时,需要volatile。这里情况不同。微软已经将他们自己的语义添加到'volatile'中,这可能是您正在考虑的内容,但这不符合ISO标准。 – paddy