此代码
procedure MyThreadTestA(const AStr: string);
快于
procedure MyThreadTestB(AStr: string);
虽然做同样的工作,但都传递指针。
然而,版本B'正确'更新AStr
的参考次数,并在我更改时复制。
版本A只传递指针,只有编译器阻止我更改AStr
。
如果我在汇编程序中使用脏技巧或以其他方式规避编译器保护,则版本A不安全,这是众所周知的但是......
通过引用传递AStr
作为const
参数线程安全吗?
如果某个其他线程中的AStr
引用计数变为零并且字符串被销毁,会发生什么?
答案 0 :(得分:16)
不,这样的技巧不是线程安全的。 Const
阻止了add-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.
答案 1 :(得分:4)
添加到Barry的答案:如果传递的字符串来自调用者范围内的局部变量,那么它绝对是线程安全的。
在这种情况下,局部变量将保存一个有效的引用,并且唯一的方法(假设只有有效的pascal代码,在asm中没有摆弄)要改变的局部变量是你的调用返回。
这还包括字符串变量的源是函数调用的结果(包括属性访问,例如TStrings.Strings [])的所有情况,因为在这种情况下编译器必须将字符串存储在本地临时变量中
只有在调用返回之前直接从可以更改该字符串的位置(通过相同或其他线程)传递字符串时,才会导致线程安全问题。