2014-10-23 2 views
1

У меня есть простой класс безопасных контейнеров. Он имеет стандартные методы добавления/удаления. Обычно перечисляя пунктов реализуются как:Можно ли безопасно использовать блокировку (TMonitor) wihtin конструктор/деструктор Enumerator?

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

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

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

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

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

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

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; 
+0

Если вы заботитесь о перфомансе, esp в многопоточном env, вы не будете перечислять счетчики на куче –

+0

@DavidHeffernan: Я хочу только быть уверенным, что после первоначальной блокировки будет следовать разблокировка после выхода из цикла for-in , –

+0

@DavidHeffernan: например, если исключение происходит внутри тела in-in, я должен быть уверен, что Unlock вызывается во всех случаях. –

ответ

2

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

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

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

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

+0

Просто любопытно: не перечисляемые всегда в куче, или вы имеете в виду, что с помощью счетчиков могут возникнуть проблемы с перфомансом вообще? –

+2

Если ваш перечислитель является записью, то выделен стек. Все мои. Однако вы не могли использовать свой замок. Записи не имеют деструктора. –

Смежные вопросы