Являются ли параметры строки `const` (thread) безопасными

Этот код

procedure MyThreadTestA(const AStr: string);

Быстрее, чем

procedure MyThreadTestB(AStr: string);

При выполнении одной и той же работы оба передают указатель.

Однако версия B "правильно" обновляет номер ссылки AStr и создает копию, если я ее меняю.
Версия A передает только указатель, и только компилятор мешает мне изменять AStr.

Версия A небезопасна, если я делаю грязные трюки в Assembler или иным образом обойти защиту компилятора, это хорошо известно, но...

Передано AStr по ссылке в качестве параметра const. Что произойдет, если счетчик ссылок AStr в каком-то другом потоке будет равен нулю, а строка будет уничтожена?

Ответ 1

Нет, такие трюки не являются потокобезопасными. Const предотвращает добавление-ref, поэтому изменения другого потока будут влиять на значение непредсказуемыми способами. Пример программы, попробуйте изменить Const в определении 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.

Но это не ограничивается только потоками; изменение базового местоположения переменных имеет похожие эффекты:

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

Ответ 2

Чтобы добавить к ответу Барри: он определенно потокобезопасен, если строка, которая была передана, получена из локальной переменной внутри области вызовов.

В этом случае эта локальная переменная будет содержать действительную ссылку и единственный способ (при условии, что только правильный код паскаля, не возиться в asm) для этой локальной переменной, которая будет изменена, если ваш вызов возвращается.

Это также включает все случаи, когда источником строковой переменной является результат вызова функции (включая доступ к свойствам, например, TStrings.Strings []), поскольку в этом случае компилятор должен хранить строку в локальной переменной темпа.

Проблемы с защитой от потоков могут возникать только в том случае, если вы напрямую передаете строку из того места, где эта строка может быть изменена (тем же или другим потоком) до того, как ваш вызов вернется.