Multithreading – is the method pointer assignment thread safe?
Example:
Suppose I have the following clues (please do not consider the contents used in the thread context execution method of this example, for illustration only):
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 suppose I want to change the method of onnotify event from the main thread at any time This main thread implements the event handling method of threadnotify method:
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;
Question:
Can you change a method that can be called from a worker thread in another thread context at any time? Is it safe to do the security shown in the above example?
I'm not sure if this is absolutely safe, at least because the method pointer is actually a pair of pointers, I don't know if I can treat it as an atomic operation
Solution
No, it's not thread safe because the operation will never be "atomic" Tnotifyevent consists of two pointers that will not be assigned at the same time: one pointer will be assigned and the other will be assigned
The 32-bit assembler generated for tnotifyevent allocation consists of two different assembler instructions, as follows:
MOV [$00000000],Object MOV [$00000004],MethodPointer
If it is a single pointer, you will have some options because the operation is atomic: the options you choose depend on the power of the CPU's memory model:
>If the CPU supports the "sequential consistency" mode, any read that occurs after writing to memory will see the new value If so, you can simply write down your value without memory barriers or using interlocking methods. > If the CPU can reorder stores and loads more easily, you need a "memory barrier" If so, the simplest solution is to use the interlockedexchangepointer
Unfortunately, I don't know how powerful the current Intel CPU memory model is There is some circumstantial evidence that some reordering may occur, and those using interlock may be recommended, but I don't see an explicit statement from Intel or another
Evidence:
>Modern CPUs use "prefetching" – this means some sort of load / store reordering. > SSE introduces the specific instructions for processing CPU cache