【问题标题】:Are `const` string parameters (thread) safe`const` 字符串参数(线程)是否安全
【发布时间】:2011-08-16 15:11:26
【问题描述】:

这段代码

procedure MyThreadTestA(const AStr: string);

procedure MyThreadTestB(AStr: string);

在做同样的工作时,两者都传递了一个指针。

但是,B 版“正确”更新了AStr 的引用计数,并在我更改时复制它。
版本 A 只传递一个指针,只有编译器阻止我更改 AStr

如果我在 Assembler 中使用肮脏的技巧或以其他方式规避编译器保护,版本 A 是不安全的,这是众所周知的,但是...

通过引用传递AStr 作为const 参数线程安全吗?
如果AStr 在其他线程中的引用计数变为零并且字符串被破坏,会发生什么?

【问题讨论】:

  • 如果引用计数在另一个线程中变为零,那么引用计数一开始就是错误的。如果两段代码都可以修改同一个字符串,那么字符串的引用计数应该大于 1,因为显然有多种方法可以引用该字符串。每个线程都应该有自己的独立变量来引用字符串,否则应该使用通常的同步技术来保护共享变量。
  • 非常好的问题。我今天学到了一些东西。

标签: string delphi parameters constants


【解决方案1】:

不,这样的技巧不是线程安全的。 Const 阻止添加引用,因此另一个线程的更改将以不可预知的方式影响该值。示例程序,尝试修改P定义中的const

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

【讨论】:

  • 实际上它甚至与多线程无关。这是关于递归的。任何具有 const 字符串参数的函数都不敢调用任何其他具有 const 字符串参数的函数,否则。坦率地说,正如我所见,修复可能相当简单。由于 volatile string 和 const string 实际上是不同的类型,因此要添加数据转换。当从 volatile 字符串代码调用 const-string 参数时 - 计数器应该在调用之前增加并在之后减少。 const 到 const 调用不受影响。 plus.google.com/+AriochThe/posts/WB3toSpAdfA
【解决方案2】:

补充巴里的回答:如果传递的字符串来自调用者范围内的局部变量,那绝对是线程安全的。

在这种情况下,该局部变量将保存一个有效的引用,并且要更改该局部变量的唯一方法(假设只是有效的 Pascal 代码,而不是在 asm 中摆弄)是如果您的调用返回。

这还包括字符串变量的源是函数调用的结果的所有情况(包括属性访问,例如 TStrings.Strings[]),因为在这种情况下,编译器必须将字符串存储在本地临时变量中.

只有在调用返回之前直接从可以更改字符串的位置(通过相同或另一个线程)传递字符串时,才会导致线程安全问题。

【讨论】:

  • ?如果字符串 var 是本地临时变量,那么是的,本地临时变量在您的调用期间是线程安全的,但是当您调用 return 时,该临时变量的实际来源仍然可以被另一个线程更改......现在,可以完全符合您的要求,但如果您想确保在调用函数时原始字符串保持不变,您应该使用某种锁定。
  • 问题是关于传递带有 const 和不带 const 的字符串,以及在被调用方法中访问该字符串的线程安全性的含义。我的答案显然是在原始问题的上下文中。你所说的是完全不同的东西,与原始问题或我的答案无关。
  • 没有争论。但是,局部变量字符串和起源于局部上下文之外的临时变量之间存在非常实际的区别。我之所以评论是因为不太懂多线程的开发人员可能不会接受这一点,并且可能会认为将 Strings[i] 作为/传递给 const 参数是“完全”线程安全的。
  • 就传递字符串而言,它是完全线程安全的。访问 TStrings 对象不是线程安全的,但这几乎不用说。
  • 它可能是线程安全的,但它不是调用安全的。如果该函数具有 const-params 并且必须调用其他函数,则最好在此之前进行特殊准备。当然,除非您编写 abd control ALL 代码,否则您可以使用 const 参数之类的技巧并负责代码安全。 github.com/the-Arioch/XE2_AutoOpenUnit/blob/master/…
猜你喜欢
  • 2011-03-11
  • 2011-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-17
  • 2016-09-05
  • 1970-01-01
相关资源
最近更新 更多