Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 793
Alister Avatar asked Feb 23 '14 20:02

Alister


People also ask

How do I know if a function is thread-safe?

A method will be thread safe if it uses the synchronized keyword in its declaration.

Is reading variables thread-safe?

As long as it's a plain variable, it's no risk. If it is a property, reading it can possibly have side effects, so is not guaranteed to be thread safe. Save this answer.

How do I use thread in Delphi?

To use a thread object in your application (and to create a descendant of Classes. TThread): Choose one: File > New > Other > Delphi Projects > Delphi Files > Thread Object.

Which of the following options is thread-safe?

Answer: Since String is immutable in Java, it's inherently thread-safe.


1 Answers

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.

like image 195
David Heffernan Avatar answered Sep 18 '22 02:09

David Heffernan