1

シンプルなスレッドセーフなコンテナ クラスがあります。標準の追加/削除メソッドがあります。通常、アイテムの列挙は次のように実装されます。

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

しかし、スレッドセーフな方法で for-in サポートを利用したい:

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

私の列挙子の実装は、コンテナーをコンストラクターでロックし、デストラクターでロックを解除します。これは機能しますが、列挙子のインスタンスが for-in ループの最初に作成され、最後に破棄されるという前提に基づいています。ここでその説明を見つけました: How is Enumerator created with for in construction destroy?

でも施錠・解錠は大事な操作なので、こういう使い方でいいのかな?

これが私の実装です:

  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;
4

1 に答える 1

2

列挙子は for ループの開始時に作成され、ループの終了時に破棄されます。列挙子の有効期間は、try/finally によって管理されます。

ただし、これについて私の言葉を鵜呑みにしないでください。ループをインストルメント化し、デストラクタがいつ呼び出されるかを確認できるデバッグ コードを追加するのは簡単です。

これは、提案されたロック戦略が適切であることを意味します。

ただし、スレッドの競合がある場合にヒープに列挙子を割り当てると、パフォーマンスの問題が発生する可能性があります。

于 2014-10-23T09:58:32.417 に答える