Assume, I would have the following thread (please, don't take into account what's used in this example's thread context execution method, it's just for explanation):
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;
Then assume, I'd like to change the method of the OnNotify
event from the main thread at any time I need. This main thread implements the event handler method as the ThreadNotify
method here:
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;
Is it safe to change a method, which can be called from a worker thread from another thread's context at any time ? Is it safe to do what is shown in the above example ?
I'm not precisely sure, if that's absolutely safe, at least since method pointer is actually a pair of pointers and I don't know if I can take it as an atomic operation.
A std::shared_ptr consists of a control block and its resource. The control block is thread-safe, but access to the resource is not. This means modifying the reference counter is an atomic operation and you have the guarantee that the resource is deleted exactly once.
In ordinary assignment involving pointers, the pointer is an alias for its target. In pointer assignment, the pointer is associated with a target. If the target is undefined or disassociated, the pointer acquires the same status as the target.
On most contemporary desktop platforms, the read/write to a word-sized, aligned location will be atomic.
No, it's not thread safe because that operation will never be "atomic". The TNotifyEvent
is made up of two pointers, and those pointers will never be both assigned at the same time: one will be assigned, then the other will be assigned.
The 32 bit Assembler generated for a TNotifyEvent
assignment is composed of two distinct assembler instructions, something like this:
MOV [$00000000], Object
MOV [$00000004], MethodPointer
If it were a single pointer then you'd have some options, since that operation is atomic: the options you have depend on how strong the memory model of the CPU is:
Interlocked
methods.Unfortunately I do not know how strong the memory model of current Intel CPUs is. There's some circumstantial evidence that suggest some re-ordering may take place, those the use of Interlocked
would be recommend, but I haven't seen a definitive statement by Intel that says one or the other.
Evidence:
Beside more than register size, there are two operations involved. A check and later executed. To minimized, create a local var and use it. But anyway, this is still not 100% thread safe
var
LNotify: TNotifyEvent;
begin
...
LNotify := FOnNotify;
if Assigned(LNotify) then
LNotify(Self);
end;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With