2011-03-30 59 views
10

我聲明瞭兩個全局變量:Delphi簡單類型線程安全嗎?

var 
    gIsRunning: Boolean = False; 
    gLogCounter: Integer = 0; 

這些變量是隻寫在主線程和其他線程讀取。在這種情況下,這些變量是否線程安全?

+5

這個問題實際上不可能以目前的形式回答。要回答它,你需要明確地指出你[線程安全的意思](http://blogs.msdn.com/b/ericlippert/archive/2009/10/19/what-is-this-thing-you-call -thread-safe.aspx)。你有一些答案認爲你的意思是「我的變量是否遭受撕裂?」。你有一個答案,處理如何通過使用鎖來自動寫入兩個變量。這兩種解釋都可能是正確的。但是我們無法分辨,因爲您沒有足夠的信息。 – 2011-03-30 08:02:40

回答

46

您可能在說關於原子變量。整數和布爾變量是原子的。布爾(字節)總是原子的,整數(32位)是原子的,因爲編譯器正確地對齊它們。

原子性意味着任何讀取或寫入操作都是作爲一個整體來執行的。如果線程A同時執行原子寫入和線程B原子讀取相同數據,則線程B讀取的數據總是一致的 - 線程B讀取的某些比特不可能從當前寫入操作獲得,並且前面寫的一些位(線程A)

但是,原子性並不意味着線程安全 - 您可以使用原子變量輕鬆編寫不安全的代碼。變量本身不能是線程安全的 - 只有整個代碼可以是(或不是)線程安全的。

+6

我在這個問題上看到的最佳彙總評論! – Misha 2011-03-30 03:55:43

+0

+1「只有代碼作爲一個整體可以(或不)線程安全」 – 2011-03-30 06:23:06

+1

是的。由於代碼由編譯器或CPU重新排序,您仍然可以使用原子變量編寫線程不安全的代碼。或者由於CPU或內核之間的內存緩存。 – 2011-03-30 11:08:59

8

不,他們不是線程安全,則必須使用例如臨界區訪問這些變量,使用InitializeCriticalSectionEnterCriticalSectionLeaveCriticalSection功能

//declaration of your global variables 
var 
    MyCriticalSection: TRTLCriticalSection; 
    gIsRunning: Boolean; 
    gLogCounter: Integer; 

//before the threads starts 
InitializeCriticalSection(MyCriticalSection); 

//Now in your thread 
    EnterCriticalSection(MyCriticalSection); 
//Here you can make changes to your variables. 
    gIsRunning:=True; 
    inc(gLogCounter); 
//End of protected block 
LeaveCriticalSection(MyCriticalSection); 
+1

以什麼方式缺乏線程安全性?有沒有機會獲得這些變量之一的無效值?讀者會看到一個不正確的值嗎? – 2011-03-30 02:24:09

+0

@Rob我的回答是在你的另一個評論。 – RRUZ 2011-03-30 02:29:18

+2

@Rob如果不鎖定,讀取和寫入的順序可能是錯誤的。 – 2011-03-30 07:59:19

13

只要有只有一個線程可以寫給他們,然後是的,他們是線程安全的。線程安全的真正問題是兩個線程試圖同時修改一個值,並且在這裏你不會有這個問題。

如果它們較大,比如記錄或數組,那麼您可能會遇到一個線程試圖寫入值,中途轉入,然後獲取上下文切換以及另一個線程讀取部分(因此損壞)數據的問題。但對於單個布爾值(1字節)和整數(4字節)值,編譯器可以自動對齊它們,使得CPU可以保證所有對它們的讀寫都是原子的,所以這不是問題。

+0

@梅森,好吧,我同意你的答案,但即使只讀了多線程訪問全局變量也可以被認爲是一個好的設計實踐嗎? – RRUZ 2011-03-30 02:13:43

+1

@RRUZ:取決於誰在考慮。我沒有看到任何地方的「通用目標良好設計實踐指南」,對嗎?所以我給出了一個技術性的答案,因爲這*可以被客觀地回答。 – 2011-03-30 02:20:04

+2

沒有@Rruz,全局變量不被認爲是很好的設計實踐。線程與此無關。 – 2011-03-30 02:20:34

11

簡單類型是「線程安全的」,只要它們可以從內存中讀取一次(或寫入一次寫入)即可。我不確定它是由CPU內存總線寬度還是它們的「整數」大小(32位與64位CPU)定義的。也許別人可以澄清那部分。

我知道閱讀的大小nowaday是至少32位。 (回到英特爾286天,一次只有8位)。

雖然有一點需要了解。儘管它一次可以讀取32位,但它不能在任何地址開始讀取。它需要是32位(或4字節)的倍數。所以,即使一個整數可以在2個後續讀取中讀取,如果它沒有對齊到32位。謝天謝地,編譯器會自動將幾乎所有的字段對齊到32位(甚至64位)。

雖然有一個例外,但是打包的記錄永遠不會對齊,因此即使是這樣的記錄中的整數也不會是線程安全的。

由於它們的大小,int64也不是線程安全的。同樣可以講述大多數浮動類型。 (除了我認爲的單數)。

現在,考慮到所有這些情況,您可以從多個線程實際編寫一個全局變量,但仍然是「線程安全的」。

例如,

var 
    LastGoodValueTested : Integer 

procedure TestValue(aiValue : Integer); 
begin 
    if ValueGood(aiValue) then 
    LastGoodValue := aiValue 
end; 

在這裏,你可以調用多個線程的常規測試值,你就不會腐敗的LastGoodValueTested變量。可能發生的情況是,寫入變量的值不會是最後一個值。 (如果線程上下文切換髮生在ValueGood(aiValue)和賦值之間)。所以,根據需要,它可能/可能不被接受。

現在,

var  
    gLogCounter: Integer = 0; 

procedure Log(S : string); 
begin 
    gLogCounter := gLogCounter + 1; 
end; 

在這裏,你可以真正腐敗櫃檯,因爲它不是一個一元操作。你首先閱讀變量。然後加1。然後你保存它。線程上下文切換可能發生在這些操作的中間。所以這是一個需要同步的情況。

在這種情況下,它可以被改寫爲

procedure Log(S : string); 
begin 
    InterlockedIncrement(gLogCounter); 
end; 

我認爲這是比使用臨界區稍快...但是,我不知道。

+0

+1迄今爲止的最好解釋。直接分配給一個字節/整數是線程安全的,執行*任何*算術和/或邏輯需要同步。 – 2011-03-30 06:19:33

+0

不,@Lieven,即使做算術也行,只要只有一個線程做它。如果所有其他線程都是讀者,那麼沒有什麼可擔心的。讀者線程將讀取變量的前一個值或新值。沒有機會閱讀一些「中間值」。 – 2011-03-30 14:24:31

+0

@Rob,當涉及多個線程時,我暗示了@Ken給出的例子。最好是明確的*(正如你現在的評論所做的那樣)* – 2011-03-30 14:36:00