2011-01-13 25 views
6

如果我有一些代碼,看起來類似:以下指導

typedef struct { 
    bool some_flag; 

    pthread_cond_t c; 
    pthread_mutex_t m; 
} foo_t; 

// I assume the mutex has already been locked, and will be unlocked 
// some time after this function returns. For clarity. Definitely not 
// out of laziness ;) 
void check_flag(foo_t* f) { 
    while(f->flag) 
     pthread_cond_wait(&f->c, &f->m); 
} 

有什麼C標準防止優化器重寫check_flag爲:

void check_flag(foo_t* f) { 
    bool cache = f->flag; 
    while(cache) 
     pthread_cond_wait(&f->c, &f->m); 
} 

在其他字,那麼生成的代碼是否有在每次遍歷循環時都遵循f指針,或者編譯器是否可以自由地將取消引用取出?

如果它自由拉出來,有沒有什麼辦法來防止這種情況?我需要在某處撒上一個易揮發的關鍵​​字嗎?它不能是check_flag的參數,因爲我計劃在這個結構中有其他變量,我不介意像這樣編譯器優化。

我可能訴諸:

void check_flag(foo_t* f) { 
    volatile bool* cache = &f->some_flag; 
    while(*cache) 
     pthread_cond_wait(&f->c, &f->m); 
} 
+0

+1在嘗試通過試錯法編寫線程代碼之前思考此類問題! – 2011-01-14 01:17:29

回答

3

通常情況下,你應該嘗試的條件對象上等待的pthread_cond_wait呼叫釋放互斥鎖之前鎖定的並行線程互斥(並在返回之前重新獲取)。所以,你的check_flag函數應該被重寫,以符合pthread條件的語義。

void check_flag(foo_t* f) { 
    pthread_mutex_lock(&f->m); 
    while(f->flag) 
     pthread_cond_wait(&f->c, &f->m); 
    pthread_mutex_unlock(&f->m); 
} 

關於編譯與否的問題,允許優化flag領域的閱讀,這answer比我解釋了它的更多細節。

基本上,編譯器知道的pthread_cond_waitpthread_mutex_lockpthread_mutex_unlock語義。他知道在這種情況下他不能優化記憶讀數(在這個例子中呼叫pthread_cond_wait)。這裏沒有記憶障礙的概念,只是對特定功能的特殊瞭解,以及在他們面前遵循的一些規則。

還有一件事情可以保護您免受處理器執行的優化。只要語義保存,您的平均處理器就可以對內存訪問進行重新排序(讀/寫),並且它總是這樣做(因爲它可以提高性能)。但是,如果多個處理器可以訪問相同的內存地址,則會中斷。內存屏障只是處理器的一條指令,告訴它它可以移動在屏障之前發佈的讀/寫並在屏障之後執行它們。它已經完成了他們。

+0

這是否意味着編譯器無法在寄存器中緩存「p-> some_flag」的值?我不確定內存屏障的影響。介意解釋一下他們? – 2011-01-14 00:14:07

+0

編輯答案。現在更清楚了嗎? – 2011-01-14 00:30:51

+0

是的,謝謝。 – 2011-01-14 00:41:11

3

正如所寫的,編譯器可以自由地緩存結果,或者以更加微妙的方式 - 將其放入寄存器中。您可以通過使變量volatile防止發生這種優化。但這不一定就夠了 - 你不應該這樣編碼!您應該按照規定使用條件變量(鎖定,等待,解鎖)。

試圖在圖書館工作很糟糕,但情況變得更糟。也許從PLDI 2005(「線程不能作爲一個庫實現」)或他的許多follow-on articles(引導修改後的C++內存模型)中讀取Hans Boehm關於一般主題的論文會讓你對上帝產生恐懼,引導你回到直線和狹窄:)。

7

在一般情況下,即使沒有參與多線程和你的循環看起來像:

void check_flag(foo_t* f) { 
    while(f->flag) 
     foo(&f->c, &f->m); 
} 

編譯器將無法緩存f->flag測試。這是因爲編譯器無法知道函數(如上面的foo())是否可能更改指向的任何對象f

在特殊情況下(foo()是可見的編譯器,並傳遞給check_flag()所有指針被稱爲不被混淆或foo()以其他方式修改)編譯器可能能夠優化檢查。

但是,pthread_cond_wait()必須以阻止優化的方式實現。

Does guarding a variable with a pthread mutex guarantee it's also not cached?

您可能也有興趣史蒂夫·傑索普的回答:Can a C/C++ compiler legally cache a variable in a register across a pthread library call?

但是你想多遠,走由貝姆的文件中提出的問題,在自己的工作是你的。據我所知,如果你想採取pthreads不能/不能做出保證的立場,那麼你基本上認爲pthreads是無用的(或者至少沒有提供安全保證,我認爲減少有相同的結果)。儘管這在最嚴格的意義上可能是正確的(如論文所述),但這也可能不是一個有用的答案。除了基於Unix的平臺上的pthread,我不確定你會有什麼選擇。

1

揮發性就是爲了這個目的。依靠編譯器來了解pthread編碼的做法似乎對我來說有點瘋狂,儘管;編譯器現在非常聰明。事實上,編譯器可能會看到你正在循環測試一個變量,並且不會因爲這個原因而將它緩存在寄存器中,而不是因爲它看到你使用pthread。如果你真的在乎,就使用volatile。

有趣的小筆記。我們有一個VOLATILE #define,它或者是「volatile」(當我們認爲這個bug不可能是我們的代碼時)或者是空白的。當我們認爲由於優化器殺死了我們而導致崩潰時,我們將其定義爲「易失性」,這使得幾乎所有事物都處於易失性狀態。然後我們測試以確定問題是否消失。到目前爲止......錯誤是開發者而不是編譯器!誰會想到!?我們開發了一個高性能的「非鎖定」和「非阻塞」線程庫。我們有一個測試平臺,每秒鐘可以處理數千場比賽。所以票價,我們從來沒有發現需要變化的問題!到目前爲止,gcc從未在寄存器中緩存共享變量。呃......我們也很驚訝。我們仍然在等待我們使用易變的機會!