Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are `const` string parameters (thread) safe

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?

like image 739
Johan Avatar asked May 01 '11 21:05

Johan


People also ask

Does const mean thread safe?

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.


2 Answers

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.
like image 82
Barry Kelly Avatar answered Oct 22 '22 15:10

Barry Kelly


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.

like image 38
Thorsten Engler Avatar answered Oct 22 '22 17:10

Thorsten Engler