In Delphi are reads on TList<x> thread safe?
delphi, multithreading, tlist
Solution
Are reads on `TList<T>` thread safe? That is to say can I have multiple threads accessing a `TList<T>` at the same time?
That is thread safe and needs no synchronisation. Multiple threads can safely read concurrently. That is equivalent to (and in fact implemented as) reading from an array. It is only if one of your threads modifies the list that synchronisation is needed.
Your code is a little more complex than this scenario. You do appear to need to cater for threads modifying the list. But you've done so with `TMultiReadExclusiveWriteSynchronizer` which is a perfectly good solution. It allows multiple reads threads to operate concurrently, but any write threads are serialized with respect to all other threads.
Problem
I've built a simple logging class and want to confirm that it is thread safe. Basically the `Log`, `RegisterLogger` and `UnRegisterLogger` will be called from different threads. `Log` will be called alot (from many different threads) and `RegisterLogger` and `UnRegisterLogger` infrequently. Basically my question can be boiled down to is: "Are reads on `TList<x>` thread safe?", that is to say can I have multiple threads accessing a `TList` at the same time. `IExecutionCounterLogger` is an interface with a Log method (with the same signature as `TExecutionCounterServer.Log`) ``` Type TExecutionCounterServer = class private Loggers : TList<IExecutionCounterLogger>; Synchronizer : TMultiReadExclusiveWriteSynchronizer; public procedure RegisterLogger(Logger : IExecutionCounterLogger); procedure UnRegisterLogger(Logger : IExecutionCounterLogger); procedure Log(const ClassName, MethodName : string; ExecutionTime_ms : integer); constructor Create; destructor Destroy; override; end; constructor TExecutionCounterServer.Create; begin Loggers := TList<IExecutionCounterLogger>.Create; Synchronizer := TMultiReadExclusiveWriteSynchronizer.Create; end; destructor TExecutionCounterServer.Destroy; begin Loggers.Free; Synchronizer.Free; inherited; end; procedure TExecutionCounterServer.Log(const ClassName, MethodName: string; ExecutionTime_ms: integer); var Logger: IExecutionCounterLogger; begin Synchronizer.BeginRead; try for Logger in Loggers do Logger.Log(ClassName, MethodName, ExecutionTime_ms); finally Synchronizer.EndRead; end; end; procedure TExecutionCounterServer.RegisterLogger(Logger: IExecutionCounterLogger); begin Synchronizer.BeginWrite; try Loggers.Add(Logger); finally Synchronizer.EndWrite; end; end; procedure TExecutionCounterServer.UnRegisterLogger(Logger: IExecutionCounterLogger); var i : integer; begin Synchronizer.BeginWrite; try i := Loggers.IndexOf(Logger); if i = -1 then raise Exception.Create('Logger not present'); Loggers.Delete(i); finally Synchronizer.EndWrite; end; end; ``` As a bit more background, this is a follow on from this question. Basically I've added some instrumentation to every method of a (DCOM) DataSnap server, also I've hooked into every TDataSnapProvider OnGetData and OnUpdateData event.