Безопасно ли использовать блокировку (TMonitor) в конструкторе/деструкторе Enumerator?

У меня есть простой потокобезопасный контейнерный класс. Он имеет стандартные методы добавления/удаления. Обычно перечисление элементов реализуется как:

MyList.lock;
try
  // looping here
finally
  MyList.unlock;
end;

Но я хочу воспользоваться поддержкой for-in потокобезопасным способом:

for item in MyList do 
begin
  // do something
end;

Моя реализация перечислителя блокирует контейнер в его конструкторе и разблокирует его в деструкторе. Это работает, но основано на предположении, что экземпляр Enumerator создается в начале цикла for-in и уничтожается в конце. Я нашел это объяснение здесь: Как создается Enumerator с for в строительстве уничтожено?

Но поскольку блокировка/разблокировка является критической операцией, мне интересно, допустимо ли такое использование?

Вот моя реализация:

  TContainer<T> = class
    private
      FPadLock: TObject;
      FItems: TList<T>;
    protected
    public
      type
        TContainerEnumerator = class(TList<T>.TEnumerator)
          private
            FContainer: TContainer<T>;
          public
            constructor Create(AContainer: TContainer<T>);
            destructor Destroy; override;
        end;
      constructor Create;
      destructor Destroy; override;
      procedure add(AItem: T);
      procedure remove(AItem: T);
      function GetEnumerator: TContainerEnumerator;
  end;

{ TContainer<T> }

procedure TContainer<T>.add(AItem: T);
begin
  TMonitor.Enter(FPadLock);
  try
    FItems.Add(AItem);
  finally
    TMonitor.Exit(FPadLock);
  end;
end;

constructor TContainer<T>.Create;
begin
  inherited Create;
  FPadLock := TObject.Create;
  FItems := TList<T>.Create;
end;

destructor TContainer<T>.Destroy;
begin
  FreeAndNil(FItems);
  FreeAndNil(FPadLock);
  inherited;
end;

procedure TContainer<T>.remove(AItem: T);
begin
  TMonitor.Enter(FPadLock);
  try
    FItems.Remove(AItem);
  finally
    TMonitor.Exit(FPadLock);
  end;
end;

function TContainer<T>.GetEnumerator: TContainerEnumerator;
begin
  result := TContainerEnumerator.Create(self);
end;

{ TContainer<T>.TContainerEnumerator }

constructor TContainer<T>.TContainerEnumerator.Create(
  AContainer: TContainer<T>);
begin
  inherited Create(AContainer.FItems);
  FContainer := AContainer;
  TMonitor.Enter(FContainer.FPadLock);  // <<< Lock parent container using Monitor
end;

destructor TContainer<T>.TContainerEnumerator.Destroy;
begin
  TMonitor.Exit(FContainer.FPadLock);  // <<< Unlock parent container
  inherited;
end;

person iPath ツ    schedule 23.10.2014    source источник
comment
Если вы заботитесь о производительности, esp в многопоточной среде, вы не будете выделять счетчики в куче.   -  person David Heffernan    schedule 23.10.2014
comment
@DavidHeffernan: я только хочу быть уверенным, что за начальной блокировкой после выхода из цикла for-in последует соответствующая разблокировка.   -  person iPath ツ    schedule 23.10.2014
comment
@DavidHeffernan: например, если в теле for-in возникает исключение, мне нужно убедиться, что Unlock вызывается во всех случаях.   -  person iPath ツ    schedule 23.10.2014


Ответы (1)


Перечислитель создается в начале цикла for и уничтожается по окончании цикла. Время жизни перечислителя управляется try/finally.

Только не верьте мне на слово. Достаточно просто добавить отладочный код, который будет управлять вашим циклом и позволит вам видеть, когда вызывается деструктор.

Это означает, что предложенная вами стратегия блокировки верна.

Я бы сказал, однако, что выделение перечислителя в куче, когда у вас есть конфликт потоков, может вызвать проблемы с производительностью.

person David Heffernan    schedule 23.10.2014
comment
Просто любопытно: разве перечислители не всегда размещаются в куче, или вы имеете в виду, что использование перечислителей может вызвать проблемы с производительностью в целом? - person iPath ツ; 23.10.2014
comment
Если ваш перечислитель является записью, он размещается в стеке. Все мои такие. Вы не могли бы использовать свой замок, хотя. Записи не имеют деструктора. - person David Heffernan; 23.10.2014