2011-05-01 13 views
10

Ten kodCzy `const` parametry String (gwint) bezpieczny

procedure MyThreadTestA(const AStr: string); 

jest szybszy niż

procedure MyThreadTestB(AStr: string); 

Podczas gdy wykonywanie tej samej pracy, zarówno przekazać wskaźnik.

Jednak wersja B "poprawnie" aktualizuje wartość odniesienia o wartości AStr i tworzy kopię, jeśli ją zmieniam.
Wersja A przekazuje tylko wskaźnik, a tylko kompilator uniemożliwia zmianę AStr.

Wersja A nie jest bezpieczne jeśli robię brudnych sztuczek w asemblerze lub w inny sposób na obejście ochrony kompilatora, to jest dobrze znane, ale ...

przepuszcza AStr przez referencyjne jako const parametry wątku bezpieczne?
Co się stanie, jeśli licznik odwołań AStr w innym wątku zostanie wyzerowany, a ciąg znaków zostanie zniszczony?

+3

Jeśli licznik odniesień osiąga wartość zerową w innym wątku, to liczenie odwołań było początkowo nieprawidłowe. Jeśli dwa fragmenty kodu mogą modyfikować ten sam ciąg znaków, liczba odwołań do łańcucha powinna być większa niż 1, ponieważ istnieje wiele sposobów odwoływania się do tego ciągu. Każdy wątek powinien mieć swoją własną niezależną zmienną do sędziowania na łańcuch, lub też zmienna dzielona powinna być chroniona zwykłymi technikami synchronizacji. –

+0

Bardzo dobre pytanie. Nauczyłem się czegoś dzisiaj. –

Odpowiedz

16

Nie, takie sztuczki nie są bezpieczne dla wątków. Const zapobiega add-ref, więc zmiany dokonane przez inny wątek wpłyną na wartość w nieprzewidywalny sposób. Przykładowy program, spróbuj zmieniając const w definicji P:

{$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. 

Ale to nie ogranicza się tylko do wątków; modyfikację podstawowej zmiennej lokalizacji ma podobne efekty:

{$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

Aby dodać odpowiedź Barry'ego: To jest na pewno bezpieczny wątku, jeśli ciąg znaków, który został przekazany pochodził z lokalnym zmiennej wewnątrz zakresu rozmówców.

W takim przypadku zmienna lokalna zachowa poprawne referencje i jedynym sposobem (zakładając tylko prawidłowy kod pascalowy, brak błądzenia w ASM), aby zmienna lokalna została zmieniona, jest połączenie przychodzące.

Obejmuje to również wszystkie przypadki, w których źródło zmiennej łańcuchowej jest wynikiem wywołania funkcji (w tym dostęp do właściwości, np. TStrings.Strings []), ponieważ w tym przypadku kompilator musi przechowywać ciąg znaków w lokalnych tempach zmienna.

Problemy z bezpieczeństwem wątków mogą być spowodowane tylko bezpośrednim przekazywaniem ciągu znaków z miejsca, w którym ten ciąg może zostać zmieniony (przez ten sam lub inny wątek) przed powrotem połączenia.

+0

? jeśli ciąg var jest lokalnym temp var, to tak, że lokalny temp var jest wątkowo bezpieczny podczas rozmowy, ale rzeczywiste pochodzenie tej zmiennej var może wciąż zostać zmienione przez inny wątek, gdy zadzwonisz do powrotu ... Teraz to może być dokładnie tak, jak chcesz, ale jeśli chcesz upewnić się, że oryginalny ciąg pozostanie taki sam podczas wywoływania funkcji, powinieneś użyć jakiegoś rodzaju blokady. –

+0

Pytanie dotyczyło przekazywania ciągu znaków const i bez niego oraz implikacji dla bezpieczeństwa wątków dostępu do tego ciągu wewnątrz wywoływanej metody. Moja odpowiedź jest oczywiście * w kontekście pierwotnego pytania *. To, o czym mówisz, jest czymś zupełnie innym, nie mającym nic wspólnego z pierwotnym pytaniem lub moją odpowiedzią. –

+0

Bez kłótni. Istnieje jednak bardzo realne rozróżnienie pomiędzy zmienną lokalną a zmienną tymczasową o pochodzeniu spoza kontekstu lokalnego. Skomentowałem to, ponieważ mniej doświadczeni programiści mogą nie zauważyć tego i mogą odejść myśląc, że przekazanie argumentu Strings [i] as/to const jest "całkowicie" bezpieczne dla wątków. –

Powiązane problemy