方法指针赋值线程是否安全?

TLa*_*ama 10 delphi multithreading

例:

假设,我将有以下线程(请不要考虑在此示例的线程上下文执行方法中使用的内容,它仅用于解释):

type
  TSampleThread = class(TThread)
  private
    FOnNotify: TNotifyEvent;
  protected
    procedure Execute; override;
  public
    property OnNotify: TNotifyEvent read FOnNotify write FOnNotify;
  end;

implementation

procedure TSampleThread.Execute;
begin
  while not Terminated do
  begin
    if Assigned(FOnNotify) then
      FOnNotify(Self); // <- this method can be called anytime
  end;
end;
Run Code Online (Sandbox Code Playgroud)

然后假设,我想OnNotify在我需要的任何时候从主线程更改事件的方法.这个主线程实现事件处理程序方法作为ThreadNotify方法:

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    FSampleThread: TSampleThread;
    procedure ThreadNotify(Sender: TObject);
  end;

implementation

procedure TForm1.ThreadNotify(Sender: TObject);
begin
  // do something; unimportant for this example
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  FSampleThread.OnNotify := nil; // <- can this be changed anytime ?
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  FSampleThread.OnNotify := ThreadNotify; // <- can this be changed anytime ?
end;
Run Code Online (Sandbox Code Playgroud)

题:

更改方法是否安全,可以随时从另一个线程的上下文中调用工作线程?做上面例子中显示的内容是否安全?

我不确定,如果那是绝对安全的,至少因为方法指针实际上是一对指针,我不知道我是否可以将它作为原子操作.

Cos*_*und 17

不,它不是线程安全的,因为该操作永远不会是"原子的".在TNotifyEvent由两个指针,并在同一时间这些指针永远不会被分配给两个:一个将被分配,那么其他的将被分配.

TNotifyEvent赋值生成的32位汇编程序由两个不同的汇编程序指令组成,如下所示:

MOV [$00000000], Object
MOV [$00000004], MethodPointer
Run Code Online (Sandbox Code Playgroud)

如果它是一个单指针,那么你就有了一些选项,因为那个操作是原子的:你所拥有的选项取决于CPU的内存模型有多强:

  • 如果CPU支持"顺序一致性"模型,则在写入内存后发生的任何读取都将保证新值.如果是这种情况,您可以简单地编写您的值,不需要内存屏障或使用Interlocked方法.
  • 如果CPU更加轻松地重新订购商店和负载,那么您需要一个"内存障碍".如果是这种情况,最简单的解决方案是使用InterlockedExchangePointer

不幸的是,我不知道当前Intel CPU的内存模型有多强.有一些间接证据表明可能会发生一些重新排序,建议使用这些重新排序Interlocked,但我还没有看到英特尔发表的明确声明中的一个或另一个.

证据:

  • 现代CPU使用"预取" - 自动暗示某种程度的加载/存储重新排序.
  • SSE介绍了处理CPU缓存的具体说明.

  • @SertacAkyuz,为什么`OnTerminate`的表现会有所不同?它仍然是一个双指针处理,编译器以相同的方式分配它.风险较低,因为它只调用一次,即便通过`Synchronize`调用它; 尽管如此,如果第三个线程(不是主线程而不是终止线程)分配它,风险绝对相同. (2认同)