【问题标题】:Is it safe to access a local variable within the context of an anonymous thread在匿名线程的上下文中访问局部变量是否安全
【发布时间】:2015-05-10 02:05:44
【问题描述】:

鉴于以下情况,匿名线程读取的 DoSomething 中 AValue 的值是否保证“可读”?即,我期望的价值?对我来说,我认为不会因为 DoSomething 在线程实际执行之前超出范围(即返回)(这可以通过意识到 x := 2 行总是在我的线程开始之前执行而很容易看出 - 尽管我猜线程所有的赌注都取消了,我的线程可能在 DoSomething 返回之前执行)。

我之所以问,是因为我在测试中从未遇到过 AValue 不等于 1(也就是传入的值)的场景,所以我想知道是否对过程和/或线程进行了一些隐式引用(再次不太可能,因为 CreateAnonymousMethod 只是创建了一个 TThread 后代(TAnonymousThread)的实例并调用我的匿名“执行”方法)。我猜测它是一样的,因为没有任何东西(在这个有限的场景中)覆盖了存储 AValue 的内存位置。

procedure TForm2.Button1Click(Sender: TObject);
var
  x: Integer;
begin
  x := 1;
  DoSomething(x);
  x := 2;// this line is only here for the purposes of placing a break point
end;

procedure TForm2.DoSomething(AValue: Integer);
begin

  TThread.CreateAnonymousThread(
    procedure
    var
      y: Integer;
    begin
      y := AValue;

      if y = 1 then
        MessageBox(0, 'Same', 'Value', MB_ICONINFORMATION or MB_OK)
      else
        MessageBox(0, 'Different', 'Value', MB_ICONINFORMATION or MB_OK)
    end).Start;
end;

编辑 只是为了验证,我想知道在匿名线程的上下文中捕获局部变量是否安全。

【问题讨论】:

    标签: multithreading delphi delphi-xe6


    【解决方案1】:

    您正在按值传递 x。这意味着当您调用DoSomething() 时,x 的值会被复制。

    因此,每当执行匿名线程时,它都不会引用 x。 该线程使用了一个捕获的变量,该变量使用原始值x 进行了初始化。

    换句话说,匿名线程无法查看您稍后在ButtonClick1 事件中是否更改x。


    注意,如果在匿名线程构建完成后,将本地AValue更改为DoSomething(),这会影响线程的结果。

    procedure TForm2.DoSomething(AValue: Integer);
    begin
    
      TThread.CreateAnonymousThread(
        procedure
        var
          y: Integer;
        begin
          y := AValue;
    
          if y = 1 then
            MessageBox(0, 'Same', 'Value', MB_ICONINFORMATION or MB_OK)
          else
            MessageBox(0, 'Different', 'Value', MB_ICONINFORMATION or MB_OK)
        end).Start;
      AValue := 3;  // <-- This value will likely be seen by the anonymous thread.
    end;
    

    如果您想避免这种情况,您可以像这样捕获AValue 值:

    procedure TForm4.DoSomething(AValue: Integer);
       function CaptureValue( y: Integer) : TProc;
       begin
         Result :=
          procedure
          begin
           if y = 1 then
             MessageBox(0, 'Same', 'Value', MB_ICONINFORMATION or MB_OK)
           else
             MessageBox(0, 'Different', 'Value', MB_ICONINFORMATION or MB_OK)
         end;
       end;
    var
      p : TProc;
    begin
      p := CaptureValue(AValue);
      TThread.CreateAnonymousThread( p)
      .Start;
      AValue := 3; // <-- The anonymous method is unaffected by this change !
    end;
    

    documentation说明外部局部变量AValue是通过匿名方法引用捕获的:

    如果匿名方法在其主体中引用外部局部变量,则该变量被“捕获”。捕获意味着延长变量的生命周期,使其与匿名方法值一样长,而不是随着它的声明例程而死。请注意,变量捕获捕获变量,而不是值。如果一个变量通过构造匿名方法被捕获后,其值发生了变化,那么匿名方法捕获的变量的值也会发生变化,因为它们是同一个变量,具有相同的存储空间。捕获的变量存储在堆上,而不是堆栈上。

    【讨论】:

    • 是的,对不起。我完全理解 x := 2 不会反映在匿名线程中。我想知道的是(在上面的例子中——而且在更复杂的情况下)线程执行时 AValue 是否保证为 1。即,它是“线程安全的”。
    • 更新了答案。如果您稍后在 DoSomething 方法中更改 AValue,那么您就有麻烦了。
    • 是的。理解。所以我可以放心地假设 AValue 将始终为 1?即我的 AnonymousThread 可以安全地访问局部变量吗?
    • 除了我概述的情况,是的。
    • 在非CaptureValue() 示例中,是的。 AValue 在调用DoSomething() 时被初始化为1,并且匿名过程捕获AValue(不是x),并且没有任何变化AValue,因此在线程运行时它始终为1。 CaptureValue() 示例中的情况并非如此。 AValue 在线程运行时更改为 3,但它并不关心,因为它首先没有捕获 AValue
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多