在Enumerators构造函数/析构函数中使用锁定(Monitor)是否安全?

时间:2014-10-23 09:29:50

标签: delphi locking enumerator tmonitor

我有简单的线程安全容器类。它具有标准的添加/删除方法。 通常枚举项目实现为:

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

但我希望以线程安全的方式利用for-in支持:

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;

1 个答案:

答案 0 :(得分:2)

枚举器是在for循环开始时创建的,并在循环结束时被销毁。枚举器的生命周期由try / finally管理。

不要只听我的话。添加一些调试代码可以很容易地修改循环,让你看到析构函数被调用的时间。

这意味着您提出的锁定策略是合理的。

我会说虽然在堆上分配一个枚举器,但是当你有线程争用时,可能会导致性能问题。