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.

4

2 回答 2

8

TList<T>在线程上读取是否安全?也就是说我可以有多个线程TList<T>同时访问a吗?

那是线程安全的,不需要同步。多个线程可以安全地并发读取。这相当于(实际上实现为)从数组中读取。只有当您的线程之一修改了需要同步的列表时。

您的代码比这种情况要复杂一些。您似乎确实需要迎合修改列表的线程。但是您已经这样做了,TMultiReadExclusiveWriteSynchronizer这是一个非常好的解决方案。它允许多个读取线程同时操作,但任何写入线程都相对于所有其他线程进行序列化。

于 2014-02-23T20:41:54.673 回答
2

强调您问题的第一部分,您声明很少调用 RegisterLogger 和 UnregisterLogger。虽然 Log 调用仅读取列表,但其他两个正在更改列表。在这种情况下,您必须确保在 Log 调用正在执行或可能发生时,这些都不会执行。

想象一下,在 Log 的 for 循环期间执行 UnregisterLogger 中的 Delete。至少结果是不可预测的。

仅在这两个写入调用中使用 Synchronizer 是不够的。

所以你的问题的答案

TList 线程上的读取是否安全?

只能是:看情况!

如果您可以确保不会发生 RegisterLogger 和 UnregisterLogger(即只能发生读取调用),您可以放心地省略 Synchronizer。否则 - 最好不要。

于 2014-02-24T00:15:24.977 回答