2011-05-01 53 views
10

此代碼是`const`字符串參數(線程)安全

procedure MyThreadTestA(const AStr: string); 

快於

procedure MyThreadTestB(AStr: string); 

雖然做同樣的工作,無論是傳遞一個指針。

但是,版本B'正確'更新AStr的引用計數,並且如果我更改它,則創建一個副本。
版本A只傳遞一個指針,只有編譯器阻止我更改AStr

版本A是不是安全的,如果我做的骯髒把戲在彙編或以其他方式規避編譯器的保護,這是衆所周知的,但...

傳遞AStr通過引用作爲const參數線程安全的?
如果AStr的某個其他線程的引用計數變爲零並且該字符串被銷燬,會發生什麼情況?

+3

如果引用計數在另一個線程中變爲零,則引用計數以錯誤開始。如果兩段代碼都可以修改相同的字符串,那麼字符串的引用計數應該大於1,因爲有明確的多種方式來引用該字符串。每個線程應該有自己的獨立變量來判斷字符串,否則應該使用通常的同步技術來保護共享變量。 – 2011-05-02 02:43:44

+0

非常好的問題。我今天學到了東西。 – 2011-05-02 20:50:36

回答

16

不,這些技巧不是線程安全的。 Const阻止了add-ref,所以由另一個線程所做的更改將以不可預知的方式影響該值。示例程序,嘗試在P定義改變const

{$apptype console} 
uses SysUtils, Classes, SyncObjs; 

type 
    TObj = class 
    public 
    S: string; 
    end; 

    TWorker = class(TThread) 
    public 
    procedure Execute; override; 
    end; 

var 
    lock: TCriticalSection; 
    obj: TObj; 

procedure P(const x: string); 
// procedure P(x: string); 
begin 
    Writeln('P(1): x = ', x); 
    Writeln('Releasing obj'); 
    lock.Release; 
    Sleep(10); // give worker a chance to run 
    Writeln('P(2): x = ', x); 
end; 

procedure TWorker.Execute; 
begin 
    // wait until TMonitor is freed up 
    Writeln('Worker started...'); 
    lock.Acquire; 
    Writeln('worker fiddling with obj.S'); 
    obj.S := 'bar'; 
    TMonitor.Exit(obj); 
end; 

procedure Go; 
begin 
    lock := TCriticalSection.Create; 
    obj := TObj.Create; 
    obj.S := 'foo'; 
    UniqueString(obj.S); 
    lock.Acquire; 
    TWorker.Create(False); 
    Sleep(10); // give worker a chance to run and block 
    P(obj.S); 
end; 

begin 
    Go; 
end. 

但它並不僅僅限於線程;修改的基本變量的位置也有類似的效果:

{$apptype console} 
uses SysUtils, Classes, SyncObjs; 

type 
    TObj = class 
    public 
    S: string; 
    end; 

var 
    obj: TObj; 

procedure P(const x: string); 
begin 
    Writeln('P(1): x = ', x); 
    obj.S := 'bar'; 
    Writeln('P(2): x = ', x); 
end; 

procedure Go; 
begin 
    obj := TObj.Create; 
    obj.S := 'foo'; 
    UniqueString(obj.S); 
    P(obj.S); 
end; 

begin 
    Go; 
end. 
4

要添加到巴里的回答是:這絕對是線程安全的,如果是得到了傳遞的字符串從呼叫者範圍內的局部變量來了。

在這種情況下,如果您的調用返回,那麼局部變量將保存有效的引用,並且唯一的方法(假設只是有效的pascal代碼,不會在asm中擺弄)才能更改該局部變量。

這還包括字符串變量的來源是函數調用的結果(包括屬性訪問,例如TStrings.Strings [])的所有情況,因爲在這種情況下,編譯器必須將字符串存儲在本地temp變量。

線程安全問題只有在您的調用返回之前直接從可以更改該字符串的位置(通過同一個或另一個線程)傳遞字符串時纔會導致線程安全問題。

+0

?如果字符串var是一個本地臨時變量,那麼在調用期間,本地臨時變量var是線程安全的,但當您調用return時,該臨時變量的實際起源仍可能被另一個線程更改...現在,可以完全按照你的要求,但如果你想確保在你調用函數時原始字符串保持不變,你應該使用某種鎖定。 – 2011-05-02 06:18:00

+0

這個問題是關於傳遞一個字符串與常量,沒有它和影響線程安全的訪問被調用的方法內的字符串。我的回答顯然是*在原始問題*的背景下。你所說的是完全不同的東西,與原始問題或我的答案無關。 – 2011-05-02 07:12:11

+0

對此沒有任何爭議。然而,局部變量字符串和臨時變量之間存在着非常真實的區別,即在本地上下文之外的原點。我評論說,因爲較少的多線程精明的開發人員可能不會接受這一點,並可能會認爲將Strings [i]傳遞給const參數是「完全」線程安全的。 – 2011-05-02 09:03:24