2010-07-30 102 views
2

在下面的代碼:易失性和多線程?

#include <pthread.h> 
#include <unistd.h> 
#include <stdio.h> 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 
int ready = 0; 

wait() 
{ 
    int i; 
    do 
    { 
     usleep(1000); 
     pthead_mutex_lock(&mutex); 
     i = ready; 
     pthread_mutex_unlock(&mutex); 
    } while (i == 0); 
    printf("Finished\n"); 
} 

signal() 
{ 
    pthead_mutex_lock(&mutex); 
    ready = 1; 
    pthread_mutex_unlock(&mutex); 
} 

我們產卵兩個線程,我們調用wait在一個線程,然後調用另一個信號我們也讓編譯器優化積極。

現在代碼的行爲會如預期的那樣,還是我們需要做好準備工作以使其發揮作用?不同的編譯器和庫會以不同的方式處理它們嗎

編輯:我希望可能有圍繞互斥體函數,會阻止優化本身或編譯器通常不優化輪迴函數調用。

注意:我還沒有編譯和測試代碼,當我有機會時會這樣做。

回答

-1

是的,這裏總是需要volatile int ready = 0;

更新
如果你想周圍的一些代碼片斷沒有優化,您可以使用#pragma指令周圍的代碼(如果你的編譯器支持它們)或 - 這是完全便攜 - 移動代碼分離文件和編譯這些文件沒有或幾乎沒有優化。
後一種方法可能仍然需要使用volatile關鍵字,因爲ready變量可能會被其他您可能想要優化的模塊使用。

+0

請參閱我的編輯。 – doron 2010-07-30 15:11:48

+0

它不便攜式。該編譯指示可能會阻止編譯器優化,但編譯器可以做的任何優化原則上也可以通過芯片組,CPU,內存控制器等來完成。 – 2011-08-30 23:14:18

2

如果編譯器在存在庫函數調用時假定有關全局變量的任何內容,我會感到驚訝。這就是說,波動不會讓你付出任何代價,並且它顯示你的意圖。我會把它放在那裏。

+1

那麼,你認爲優化器不會查看這些庫函數的代碼?今天可能是這樣,但可能不是明年。 – Darron 2010-07-30 15:16:34

+0

@達隆:編譯器無法看到它從DLL加載的代碼。 – Puppy 2010-07-30 15:19:24

+0

並非所有的庫都是DLL的。並不是所有的優化器都在編譯時運行;已經有針對機器代碼的即時優化器的實驗。 – Darron 2010-07-30 15:31:36

-1

存在優化時需要揮發性。否則,就緒的讀取可以合法移出while循環。

假設現在對標準沒有承諾的優化限制現在可能沒什麼問題,但是隨着編譯器的改進,將會給未來的維護者帶來巨大的悲痛。

+0

將即時讀取移出while循環會違反POSIX標準。 – 2011-08-31 06:59:34

0

現在代碼將按預期工作,或者我們需要做好準備工作以使其正常工作嗎?

我會建議在這種情況下使用volatile。雖然在這種情況下似乎並不需要。

IOW,我個人會添加volatile並刪除鎖定:它不需要設置/讀取變量的值。

不同的編譯器和庫會以不同的方式處理這個問題嗎? 我希望可能會有一些圍繞互斥函數的事情,它們會阻止圍繞自身進行優化,或者編譯器通常不會優化循環函數調用。

在你的情況下,調用函數(pthread_mutex_lock()的)具有副作用和改變執行環境。因此,編譯器必須重新讀取全局變量,該變量可能會因調用該函數而發生變化。

要確定的是,您想諮詢C99的5.1.2.3 Program execution從哪裏借用了術語。爲了讓你的味道:

[...]訪問一個volatile對象,修改對象,修改一個文件,或調用任何做這些操作都是副作用,這是變化的函數處於執行環境的狀態。評估表達可能會產生副作用。在執行順序中的某些特定點上,稱爲順序點,先前評估的所有副作用都應完整,且不會發生後續評估的副作用。 (附錄C中給出了序列點的摘要。)

在抽象機器中,所有表達式都按照語義指定進行評估。一個實際的實現不需要評估表達式的一部分,如果它可以推斷出它的值沒有被使用並且沒有產生任何需要的副作用(包括由調用一個函數或訪問一個volatile對象引起的任何副作用)。 [...]從附錄C

摘錄:

以下是5.1.2.3中描述的序列點:
- 給一個函數調用,該參數已經被評估之後(6.5.2.2)。

並從那裏。

根據我的經驗,編譯器現在足夠聰明,甚至在積極優化時也不會對全局變量的循環控制變量做任何事情。

+0

即使添加了volatile關鍵字,也需要鎖定。這有兩個原因。更新整數可能不是原子的。即使寫入是原子性的,在具有單獨緩存的多核機器上,也不能保證讀取和寫入將按順序發生。確保按順序發生的唯一方法是插入內存屏障指令。鎖將確保這肯定發生。 – doron 2010-07-30 23:12:22

+0

@ deus-ex-machina399:關於原子性。幾乎所有的拱都有int小於或等於cpu字。這使得讀/寫操作成爲原子。 (增加/減少/等 - 不是原子的,因爲它們結合了多個操作:讀取,更改值,寫入。)關於排序。在上面的簡單讀/寫代碼的情況下,緩存一致性保證就足夠了。 http://en.wikipedia.org/wiki/Cache_coherence – Dummy00001 2010-07-31 09:39:12

+0

如果我們在調用signal之前寫入一個全局變量(如myval),然後另一個線程在等待退出後讀取該變量,那麼您將遇到問題。如果沒有內存屏障,由於讀取和寫入的重新排序,等待線程可能會在全局變量(myval)被設置之前獲得讀取準備。數據存儲屏障將確保不會發生。請參閱http://software.intel.com/zh-cn/blogs/2007/11/30/volatile-almost-useless-for-multi-threaded-programming/ – doron 2010-07-31 13:20:43

1

揮發性既不需要也不足夠。所以沒有理由使用它。這是不夠的,因爲沒有標準指定它將提供線程之間的可見性。這是沒有必要的,因爲pthreads標準說互斥量就足夠了。

更糟糕的是,使用它表明你是一個無能的程序員,他試圖在你的代碼上撒上魔法灰塵來讓它工作。它充滿了對貨物崇拜的編程,任何查看代碼的人都會推斷你不知道它不是必需的。更糟糕的是,他們可能認爲你覺得已經足夠了,並且他們會懷疑你寫的任何其他代碼,擔心你使用「volatile」隱藏多線程錯誤而不是修復它們。

+0

我很好奇,知道一個像本地POSIX線程庫這樣的pthread實現如何處理共享變量的情況,編譯器? – 2014-01-13 14:47:47

+0

@ManuelSelva每個實現都可以處理它,但它是想要的。通常,沒有什麼特別的你需要做。任何另一個線程都可以訪問共享變量的方式,另一個編譯單元中的函數也可以訪問它,所以編譯器已經做了正確的事情。 – 2014-01-13 17:17:35