This code
procedure MyThreadTestA(const AStr: string);
Is faster than
procedure MyThreadTestB(AStr: string);
Whilst doing the same work, both pass a pointer.
However version B 'correctly' updates the referencecount of AStr
and makes a copy if I change it.
Version A passes just a pointer and only the compiler prevents me from changing AStr
.
Version A is not safe if I do dirty tricks in Assembler or otherwise to circumvent the compiler protection, this is well known but...
Is passed AStr
by reference as a const
parameters thread safe?
What happens if AStr
's reference count in some other thread goes to zero and the string is destroyed?
In conclusion, const does mean thread-safe from the Standard Library point of view. It is important to note that this is merely a contract and it won't be enforced by the compiler, if you break it you get undefined behavior and you are on your own.
No, such tricks are not thread-safe. Const
prevents the add-ref, so changes by another thread will affect the value in unpredictable ways. Sample program, try altering the const
in the definition of 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.
But it's not just limited to threads; modifying the underlying variable location has similar effects:
{$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.
To add to Barry's answer: It is definitely thread-safe if the string that got passed came from a local variable inside the callers scope.
In that case that local variable will hold a valid reference and the only way (assuming just valid pascal code, no fiddling around in asm) for that local variable to be changed is if your call returns.
This also includes all cases where the source of the string variable is the result of a function call (including property access, e.g. TStrings.Strings[]) because in this case the compiler has to store the string in a local temp variable.
Thread-safety problems can only result if you are directly passing a string from a location where that string can be changed (by the same or another thread) before your call returns.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With