示例:
假设,我有以下线程(请不要考虑本示例的线程上下文执行方法中使用的内容,这只是为了解释):
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;
问题:
更改可以随时从另一个线程的上下文中从工作线程调用的方法是否安全?执行上面示例中显示的操作安全吗?
我不确定这是否绝对安全,至少因为方法指针实际上是一对指针,我不知道是否可以将其视为原子操作。
最佳答案
不,它不是线程安全的,因为该操作永远不会是“原子”的。 TNotifyEvent
由两个指针组成,这些指针永远不会同时被赋值:一个将被赋值,然后另一个将被赋值。
为 TNotifyEvent
赋值生成的 32 位汇编器由两个不同的汇编器指令组成,如下所示:
MOV [$00000000], Object
MOV [$00000004], MethodPointer
如果它是单个指针,那么您将有一些选择,因为该操作是原子的:您拥有的选项取决于 CPU 内存模型的强度:
- 如果CPU支持“顺序一致性”模型,那么在写入内存之后发生的任何读取都将看到新值,这是保证的。如果是这种情况,您可以简单地写入您的值,无需内存屏障或使用
Interlocked
方法。 - 如果 CPU 对于重新排序存储和加载更加宽松,那么您需要一个“内存屏障”。如果是这种情况,最简单的解决方案是使用 InterlockedExchangePointer
遗憾的是,我不知道当前 Intel CPU 的内存模型有多强。有一些间接证据表明可能会进行一些重新排序,建议使用Interlocked
,但我还没有看到英特尔明确声明其中之一。
证据:
- 现代 CPU 使用“预取”——这会自动意味着某种程度的加载/存储重新排序。
- SSE 引入了处理 CPU 缓存的具体指令。
关于multithreading - 方法指针赋值线程安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14896734/