10

例子:

假设,我会有以下线程(请不要考虑这个例子的线程上下文执行方法中使用了什么,它只是为了解释):

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;

然后假设,我想随时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;

问题:

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

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

4

2 回答 2

17

不,它不是线程安全的,因为该操作永远不会是“原子的”。由TNotifyEvent两个指针组成,这些指针永远不会同时被赋值:一个被赋值,另一个被赋值。

为赋值生成的 32 位汇编器TNotifyEvent由两条不同的汇编器指令组成,如下所示:

MOV [$00000000], Object
MOV [$00000004], MethodPointer

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

  • 如果 CPU 支持“顺序一致性”模型,那么在您写入内存之后发生的任何读取都会看到新值,这是有保证的。如果是这种情况,您可以简单地编写您的值,不需要内存屏障或使用Interlocked方法。
  • 如果 CPU 对重新排序存储和加载更放松,那么您需要一个“内存屏障”。如果是这种情况,最简单的解决方案是使用InterlockedExchangePointer

不幸的是,我不知道当前 Intel CPU 的内存模型有多强大。有一些间接证据表明可能会发生一些重新订购,Interlocked推荐使用那些,但我还没有看到英特尔的明确声明说其中一个或另一个。

证据:

  • 现代 CPU 使用“预取”——这会自动暗示某种程度的加载/存储重新排序。
  • SSE 引入了处理 CPU 缓存的具体指令。
于 2013-02-15T14:26:52.880 回答
2

除了寄存器大小之外,还涉及两个操作。检查并稍后执行。要最小化,请创建一个本地 var 并使用它。但无论如何,这仍然不是 100% 线程安全的

var
  LNotify: TNotifyEvent;
begin
  ...
  LNotify := FOnNotify;
  if Assigned(LNotify) then
    LNotify(Self);
end;
于 2013-02-15T17:10:49.787 回答