【发布时间】:2013-02-15 14:22:20
【问题描述】:
例子:
假设,我会有以下线程(请不要考虑这个例子的线程上下文执行方法中使用了什么,它只是为了解释):
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;
问题:
更改一个方法是否安全,该方法可以随时从另一个线程的上下文中从工作线程调用?执行上述示例中的操作是否安全?
我不确定,如果这绝对安全,至少因为方法指针实际上是一对指针,我不知道我是否可以将其视为原子操作。
【问题讨论】:
-
不要这样做,而是始终分配它并使用 32 位变量而不是整数或本机整数类型。然后为整数分配一个非零值或零值。如果整数值非零,则调用事件,否则不调用事件。这是一个简单的解决方法。
-
@bobshacks,这在多 CPU(或多核)系统上是不安全的,除非您每次分配该标记整数时都会造成内存屏障。由于没有障碍,一些 CPU 将在分配后一段时间内使用缓存中的“陈旧”数据。您需要
InterlockedXYZ例程,但它可以工作。 -
是的。使用 InterlockedExchange 函数进行分配。在这种情况下,InterlockedIncrement 和 InterlockedDecrement 更好。另一个想法是创建一个指向方法指针的指针。因此,将其作为表单中的变量,然后将指向该变量的指针传递给线程,自然而然地使用原子函数来执行此操作。那么它仍然只是一个指针。
-
@bobshacks,谢谢!我想我可以应付这种情况。这个问题我只是出于好奇而问的(实际上,我不会真的这样做),并希望它对其他人也有帮助,因为方法分配并不那么明显,人们可以忽略这种风险.
-
@CosminPrund 英特尔上的德尔福实际上不是问题。例如,双重检查锁定对于 Intel 上的 Delphi 工作得很好,正因为如此。英特尔 x86 和 x64 具有强大的内存模型,Delphi 不会优化或缓存来自全局可见的任何内容(即不是局部变量的所有内容)的读取和写入。
标签: multithreading delphi