🙂

Wrong assumptions

function _IntfClear(var Dest: IInterface): Pointer; var P: Pointer; begin Result := @Dest; if Dest <> nil then begin P := Pointer(Dest); Pointer(Dest) := nil; IInterface(P)._Release; end; end; procedure _IntfCopy(var Dest: IInterface; const Source: IInterface); var P: Pointer; begin P := Pointer(Dest); if Source <> nil then Source._AddRef; Pointer(Dest) := Pointer(Source); if P <> nil then IInterface(P)._Release; end;

var Data, Tmp: IInterface; Data := nil; // Thread 1 -> _IntfClear Tmp := Data; // Thread 2 -> _IntfCopy

Example of thread unsafe code

uses System.SysUtils, System.Classes; var Data: IInterface; procedure Test; var Tmp: IInterface; i, j: Integer; begin for i := 0 to 1000 do begin Data := TInterfacedObject.Create; TThread.CreateAnonymousThread( procedure var i: Integer; begin for i := 0 to 10 do Sleep(15); Data := nil; end).Start; for j := 0 to 1000000 do begin Tmp := Data; if not Assigned(Tmp) then break; Tmp := nil; end; end; end; begin try Test; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Writeln('Finished'); end.

Have some fun

🙂





Multithreading can be hard to do right. The most common point of failure is assuming some code is thread safe when it actually is not. And then the whole multithreading castle crumbles into ruins."Thread safe" is a pretty vague term. If you are not sure what it actually means, I suggest you start by reading Eric Lippert's blog post on the subject. On the other hand, if you do know what thread safety is, well, I suggest you read it anywayWhen it comes to thread safety in Delphi (actually, this is not a Delphi-specific thing) there is very little built in by default. Basically, if you are writing some code that has to be thread safe, you have to take care of the thread safety part all by yourself. And you have to be very careful with your assumptions, as you can easily come to the wrong conclusions.To demonstrate how deeply unsafety goes, and how easy is to make wrong assumptions, I will use ARC as an example.Of course, accessing the content of an object instance is not thread safe in terms of having multiple threads reading and writing to that content. But what about references? If you don't have to change the content of the object instance across multiple threads, then surely you can safely handle its references and its lifetime across thread boundaries without additional safety mechanisms - locks? After all, reference counting itself uses a locking mechanism to ensure a consistent reference count.If you think ARC references are thread safe, think again. They are not thread safe at all. Not even close. Even something as trivial as the assignment of one reference to another can lead to disaster in a multithreaded scenario. To be fair, assignment of anything but the most basic simple types is not trivial at all.The only thread safe part of the reference counting mechanism is keeping the reference count variable in a consistent state. That variable and that variable alone is protected from being simultaneously accessed from multiple threads during reference count increments or decrements. And there is more code involved in assigning one reference to another than in changing the reference count variable.Assigning nil - clearing the reference - calls theorhelper functions, depending whether you are dealing with an interface reference under all compilers, or an object reference under ARC compilers. Assigning one reference to another calls theorhelper functions. There is very little difference between functions that handle interface references and ones that handle object references, so we can freely focus on the former to illustrate assignment behavior.If you have multiple threads accessing - reading and writing - the same reference, one thread can easily interfere with the other. Let's say we have a shared reference, pointing to a previously created object, one thread that sets that reference to nil, and another thread that tries to take another strong reference from that one with an assignment to another variable. The first thread will execute thefunction that will result in object destruction. The second thread, trying to grab a strong reference preventing object destruction, will execute thefunction.If theline from thefunction executes before the call to Imanages to decrease the reference count to zero and subsequently calls the object's destructor, all is good. The second thread will successfully capture another strong reference to the object instance. However, the first thread can interrupt the second thread at the wrong moment, just after thecheck was successfully passed, but beforehad the chance to increment the object's reference count. In that case, the first thread will happily destroy the object instance while the second thread will happily grab a strong reference to an already nuked object and you can forget about "happily ever after".If you want to observe broken ARC in action, you can run the following code and watch the invalid pointer operations dropping in. That code is equally broken on all Delphi compilers, classic or ARC. Please note, when I say broken, I am not implying a bug in the compiler. It is merely an example of thread unsafe code.And at the end, if you want to have some fun, you can visit The Deadlock Empire and test your multithreading skills at the same time