In Delphi are reads on TList<x> thread safe?
Asked Answered
T

2

6

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.

Torpedoman answered 23/2, 2014 at 20:39 Comment(0)
R
8

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.

Raddy answered 23/2, 2014 at 20:41 Comment(0)
L
2

Emphasizing the first part of your question, you state that calls to RegisterLogger and UnregisterLogger are infrequently. While the Log call is only reading the list, these other two are changing the list. In this case you have to make sure that none of these is executed while a Log call is executing or may occur.

Imagine a Delete in UnregisterLogger is executed during the for loop in Log. The results are unpredictable at least.

It will be not sufficient to use the Synchronizer only in those two writing calls.

So the answer to your question

Are reads on TList thread safe?

can only be: it depends!

If you can make sure that no RegisterLogger and UnregisterLogger happen (i.e. only read calls can happen), you can safely omit the Synchronizer. Otherwise - better not.

Lovegrass answered 24/2, 2014 at 0:15 Comment(4)
The synchroniser is TMultiReadExclusiveWriteSynchronizerRaddy
Perhaps I didn't get the question right. Using the Synchronizer is obviuosly thread safe (thats the whole purpose). I understood Alister that he wanted to omit it for the read part.Lovegrass
No, Alister was asking if TList<T> reads will function correctly when multiple "readers" will run at the same time.Lot
@gabr, that is more or less mentioned in the last paragraph of my answer. I just wanted to emphasize that "multiple readers" should better be worded "multiple readers and no writers". If you take the question literally it says: "Are reads on TList<x> thread safe?" This cannot unconditionally answered with a yes. But probably I am nitpicking here.Lovegrass

© 2022 - 2024 — McMap. All rights reserved.