В Delphi чтение в TList‹x› потокобезопасно?

Я создал простой класс ведения журнала и хочу убедиться, что он потокобезопасен. В основном Log, RegisterLogger и UnRegisterLogger будут вызываться из разных потоков. Log будет вызываться часто (из многих разных потоков), а RegisterLogger и UnRegisterLogger редко.

По сути, мой вопрос можно свести к следующему: «Являются ли чтения в 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;

В качестве дополнительной информации это следует из этого вопроса. По сути, я добавил некоторый инструментарий к каждому методу (DCOM) сервера DataSnap, а также подключился к каждому событию TDataSnapProvider OnGetData и OnUpdateData.


person Alister    schedule 23.02.2014    source источник


Ответы (2)


Безопасно ли чтение в потоке TList<T>? То есть могу ли я одновременно иметь несколько потоков, обращающихся к TList<T>?

Это потокобезопасно и не требует синхронизации. Несколько потоков могут безопасно читать одновременно. Это эквивалентно (и фактически реализовано) чтению из массива. Синхронизация необходима только в том случае, если один из ваших потоков изменяет список.

Ваш код немного сложнее, чем этот сценарий. Похоже, вам нужно обслуживать потоки, изменяющие список. Но вы сделали это с TMultiReadExclusiveWriteSynchronizer, что является отличным решением. Это позволяет нескольким потокам чтения работать одновременно, но любые потоки записи сериализуются по отношению ко всем другим потокам.

person David Heffernan    schedule 23.02.2014

Подчеркивая первую часть вашего вопроса, вы заявляете, что вызовы RegisterLogger и UnregisterLogger происходят нечасто. В то время как вызов Log только читает список, эти два других изменяют список. В этом случае вы должны убедиться, что ни один из них не выполняется во время выполнения или может произойти вызов журнала.

Представьте, что Delete в UnregisterLogger выполняется во время цикла for в Log. По крайней мере, результаты непредсказуемы.

Недостаточно использовать синхронизатор только в этих двух вызовах записи.

Итак, ответ на ваш вопрос

Являются ли чтения в TList потокобезопасными?

может быть только: это зависит!

Если вы можете убедиться, что не происходит ни RegisterLogger, ни UnregisterLogger (т. е. могут происходить только вызовы чтения), вы можете безопасно опустить Synchronizer. В противном случае - лучше не надо.

person Uwe Raabe    schedule 24.02.2014
comment
Синхронизатор — TMultiReadExclusiveWriteSynchronizer. - person David Heffernan; 24.02.2014
comment
Возможно, я не правильно понял вопрос. Использование Synchronizer, очевидно, является потокобезопасным (в этом вся цель). Я понял Алистера, что он хотел опустить это для прочитанной части. - person Uwe Raabe; 24.02.2014
comment
Нет, Алистер спрашивал, будет ли чтение TList‹T› работать правильно, когда несколько считывателей будут работать одновременно. - person gabr; 24.02.2014
comment
@gabr, это более или менее упомянуто в последнем абзаце моего ответа. Я просто хотел подчеркнуть, что несколько читателей лучше сформулировать как несколько читателей, а не писателей. Если вы понимаете вопрос буквально, он гласит: безопасно ли чтение в потоке TList‹x›? На это нельзя безоговорочно ответить «да». Но, наверное, я тут придираюсь. - person Uwe Raabe; 25.02.2014