简体   繁体   English

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

[英]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. 基本上,将从不同的线程调用LogRegisterLoggerUnRegisterLogger Log will be called alot (from many different threads) and RegisterLogger and UnRegisterLogger infrequently. Log将很少被调用(来自许多不同的线程)和RegisterLoggerUnRegisterLogger很少。

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

IExecutionCounterLogger is an interface with a Log method (with the same signature as TExecutionCounterServer.Log ) 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;

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. 基本上我已经为(DCOM)DataSnap服务器的每个方法添加了一些工具,我也已经连接到每个TDataSnapProvider OnGetData和OnUpdateData事件。

Are reads on TList<T> thread safe? 读取TList<T>线程是否安全? That is to say can I have multiple threads accessing a TList<T> at the same time? 也就是说我可以让多个线程同时访问TList<T>吗?

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. 但是你已经使用TMultiReadExclusiveWriteSynchronizer这样做了,这是一个非常好的解决方案。 It allows multiple reads threads to operate concurrently, but any write threads are serialized with respect to all other threads. 它允许多个读取线程同时操作,但任何写入线程都相对于所有其他线程进行序列化。

Emphasizing the first part of your question, you state that calls to RegisterLogger and UnregisterLogger are infrequently. 强调问题的第一部分,您声明对RegisterLogger和UnregisterLogger的调用很少。 While the Log call is only reading the list, these other two are changing the list. 虽然Log调用只是读取列表,但其他两个正在更改列表。 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. 想象一下,在Log中的for循环期间执行UnregisterLogger中的Delete。 The results are unpredictable at least. 结果至少是不可预测的。

It will be not sufficient to use the Synchronizer only in those two writing calls. 仅在这两个写入调用中使用Synchronizer是不够的。

So the answer to your question 所以你的问题的答案

Are reads on TList thread safe? 读取TList线程是否安全?

can only be: it depends! 只能是:这取决于!

If you can make sure that no RegisterLogger and UnregisterLogger happen (ie only read calls can happen), you can safely omit the Synchronizer. 如果您可以确保没有RegisterLogger和UnregisterLogger发生(即只能进行读取调用),您可以安全地省略Synchronizer。 Otherwise - better not. 否则 - 最好不要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM