2012-09-17 21 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