在Delphi中读取TList <x>线程安全吗?</x>

时间:2014-02-23 20:39:25

标签: multithreading delphi tlist

我已经构建了一个简单的日志记录类,并希望确认它是线程安全的。基本上,LogRegisterLoggerUnRegisterLogger将从不同的线程调用。 Log将很多(来自许多不同的主题)和RegisterLoggerUnRegisterLogger很少被调用。

基本上我的问题可归结为:“TList<x>线程读取是否安全?”,也就是说我可以让多个线程同时访问TList

IExecutionCounterLogger是一个带有Log方法的接口(与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;

作为更多背景,这是this question的后续内容。基本上我已经为(DCOM)DataSnap服务器的每个方法添加了一些工具,我也已经连接到每个TDataSnapProvider OnGetData和OnUpdateData事件。

2 个答案:

答案 0 :(得分:8)

  

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

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

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

答案 1 :(得分:2)

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

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

仅在这两次写入调用中使用同步器是不够的。

所以问题的答案

  

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

只能是:它取决于!

如果可以确保没有RegisterLogger和UnregisterLogger发生(即只能进行读取调用),则可以安全地省略Synchronizer。否则 - 最好不要。