1

Delphiでオブジェクトのプールを実装しています。プールからオブジェクトを取得するには、スレッドを同期する必要があります。

スレッドコード:

uClientQueryPool.CLIENT_POOL_GUARD.Acquire();
QueryClient := QUERY_POOL.GetClient();
uClientQueryPool.CLIENT_POOL_GUARD.Release;

プールコード:

var
   CLIENT_POOL_GUARD: TCriticalSection;

type
   TClientQueryPool = class
public
   function GetClient(): TQueryClient;
end;

CLIENT_POOL_GUARDは単位変数です。プールは正常に機能していますが、「uClientQueryPool.CLIENT_POOL_GUARD.Acquire();」を使用できますか?および「uClientQueryPool.CLIENT_POOL_GUARD.Release;」GetClientメソッド内?

このような:

function TClientQueryPool.GetClient: TQueryClient;
begin
    CLIENT_POOL_GUARD.Acquire();
    ...
    CLIENT_POOL_GUARD.Release;
end;
4

3 に答える 3

2

クリティカルセクションインスタンスをプールクラスのプライベートメンバーにするのと同様に、get / pop/whateverメソッド内でロックを移動することは問題ありません。オブジェクトをプールにプッシュバックするrelease()呼び出しで同じCSを使用します。

通常、TObjectQueueをプールキューとして使用し、CSを保護し、セマフォを使用してプールの内容をカウントし、プールが一時的に空になった場合にスレッドをブロックするように要求するために、これを数十年にわたって行ってきました。

その「二重取得」スレッドがどこから来たのかわからない。ロックはプールクラスの内側か外側のどちらかにあります。なぜ誰かが両方をコーディングするのか、私は本当に想像できません!

クラスの例:

まず、プールされたオブジェクトを保持するためのスレッドセーフなPCキュー:

unit tinySemaphoreQueue;

interface

uses
  Windows, Messages, SysUtils, Classes,syncObjs,contnrs;


type

pObject=^Tobject;


TsemaphoreMailbox=class(TobjectQueue)
private
  countSema:Thandle;
protected
  access:TcriticalSection;
public
  property semaHandle:Thandle read countSema;
  constructor create; virtual;
  procedure push(aObject:Tobject); virtual;
  function pop(pResObject:pObject;timeout:DWORD):boolean;  virtual;
end;


implementation

{ TsemaphoreMailbox }

constructor TsemaphoreMailbox.create;
begin
  inherited Create;
  access:=TcriticalSection.create;
  countSema:=createSemaphore(nil,0,maxInt,nil);
end;

function TsemaphoreMailbox.pop(pResObject: pObject;
  timeout: DWORD): boolean;
begin // wait for a unit from the semaphore
  result:=(WAIT_OBJECT_0=waitForSingleObject(countSema,timeout));
  if result then // if a unit was supplied before the timeout,
  begin
    access.acquire;
    try
      pResObject^:=inherited pop; // get an object from the queue
    finally
      access.release;
    end;
  end;
end;

procedure TsemaphoreMailbox.push(aObject: Tobject);
begin
  access.acquire;
  try
    inherited push(aObject); // shove the object onto the queue
  finally
    access.release;
  end;
  releaseSemaphore(countSema,1,nil); // release one unit to semaphore
end;

end.

次にオブジェクトプール:

unit tinyObjectPool;

interface

uses
  Windows, Messages, SysUtils, Classes,syncObjs,contnrs,
  tinySemaphoreQueue;

type
  TobjectPool=class;

  TpooledObject=class(TObject)
  private
    FmyPool:TObjectPool;
  protected
    Fparameter:TObject;
  public
    procedure release;
    constructor create(parameter:TObject); virtual;
  end;

  TpooledObjectClass=class of TpooledObject;

  TobjectPool=class(TsemaphoreMailbox)
  private
    Fparameter:TObject;
    function getPoolLevel: integer;
  public
    property poolLevel:integer read getPoolLevel;
    constructor create(poolDepth:integer;
      pooledObjectClass:TpooledObjectClass;parameter:TObject); reintroduce; virtual;
  end;

implementation

{ TobjectPool }

constructor TobjectPool.create(poolDepth: integer;
  pooledObjectClass: TpooledObjectClass;parameter:TObject);
var objectCount:integer;
    thisObject:TpooledObject;
begin
  inherited create;
  Fparameter:=parameter; // a user parameter passed to all objects
  for objectCount:=0 to poolDepth-1 do // fill up the pool with objects
  begin
    thisObject:=pooledObjectClass.create(parameter);
    thisObject.FmyPool:=self;
    inherited push(thisObject);
  end;
end;

function TobjectPool.getPoolLevel: integer;
begin
  access.acquire;
  result:=inherited count;
  access.release;
end;



{ TpooledObject }

constructor TpooledObject.create(parameter: TObject);
begin
  inherited create;
  Fparameter:=parameter;
end;

procedure TpooledObject.release;
begin
  FmyPool.push(self);
end;

end.
于 2012-08-30T16:45:07.170 に答える
2

はい、できます。ただし、スレッドセーフな方法でプールからオブジェクトをプルすることはできますが、オブジェクト自体がスレッドセーフでない場合、それを使用することはスレッドセーフではない可能性があることに注意してください。たとえば、以下の例では、プールはスレッド セーフであり、プール内のすべてのオブジェクトが使用中の場合はスレッドを待機させますが、オブジェクトが使用中になると、グローバル データを使用するため、そのオブジェクトを使用してもスレッド セーフではありません。

uses
  SyncObjs;

var
  GlobalData: Integer = 0;

type
  TDataObject = class
    Used: Boolean;
    procedure UpdateData;
  end;

type
  TPool = class
    FLock: TCriticalSection;
    FSemaphore: TSemaphore;
    FDataObjects: array[0..9] of TDataObject;
    constructor Create;
    destructor Destroy; override;
    function GetDataObject: TDataObject;
    procedure ReleaseDataObject(AObject: TDataObject);
  end;

var
  Pool: TPool;

type
  TDataThread = class(TThread)
    constructor Create;
    procedure Execute; override;
  end;

{ TPool }

constructor TPool.Create;
var
  i: Integer;
begin
  inherited Create;
  FLock := TCriticalSection.Create;
  FSemaphore := TSemaphore.Create(nil, Length(FDataObjects), Length(FDataObjects), '', False);

  for i := Low(FDataObjects) to High(FDataObjects) do
    FDataObjects[i] := TDataObject.Create;
end;

destructor TPool.Destroy;
var
  i: Integer;
begin
  for i := Low(FDataObjects) to High(FDataObjects) do
    FDataObjects[i].Free;

  FSemaphore.Free;
  FLock.Free;
end;

function TPool.GetDataObject: TDataObject;
var
  i: Integer;
begin
  Result := nil;

  FLock.Acquire;
  try
    FSemaphore.Acquire;
    for i := Low(FDataObjects) to High(FDataObjects) do
      if not FDataObjects[i].Used then
      begin
        Result := FDataObjects[i];
        Result.Used := True;
        Exit;
      end;

    Assert(Result <> nil, 'Pool did not return an object');
  finally
    FLock.Release;
  end;
end;

procedure TPool.ReleaseDataObject(AObject: TDataObject);
begin
  if not AObject.Used then
    raise Exception.Create('Data object cannot be released, because it is not in use.');

  AObject.Used := False;
  FSemaphore.Release;
end;

{ TDataObject }

procedure TDataObject.UpdateData;
begin
  Inc(GlobalData);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  TDataThread.Create;
end;

{ TDataThread }

constructor TDataThread.Create;
begin
  inherited Create(True);
  FreeOnTerminate := True;
  Resume;
end;

procedure TDataThread.Execute;
var
  DataObject: TDataObject;
begin
  DataObject := Pool.GetDataObject;

  DataObject.UpdateData; // <-- Not thread-safe!

  Pool.ReleaseDataObject(DataObject);
end;

initialization
  Pool := TPool.Create;
finalization
  Pool.Free;
end.
于 2012-08-30T17:38:04.153 に答える
-3

1) スレッド コードから取得/解放コードを削除します。脆弱です。あるスレッドでは、それを呼び出すのを忘れてしまいます。経験則として、セキュリティ対策は、クライアントであいまいに分散されるのではなく、サーバーによって集中化および実施される必要があります。

2) Acquire/Release 呼び出しはエラーから保護する必要があります。そうしないと、浮遊例外がすべてのスレッドを永久にロックしてしまいます。

 function TClientQueryPool.GetClient: TQueryClient;
 begin
   CS.Acquire;
   try
     // actually getting object, preferably just calling
     // internal non-public thread-unsafe method for it
   finally
     CS.Release;
   end;
  end;

3) クリティカル セクション自体は、プールの内部の非パブリック メンバーである必要があります。そうすれば、将来、実装の詳細を忘れたときに、次のような簡単なリファクタリングが可能になります。

3.1) 複数のプールの実装

3.2) プールコードを別のユニットに移動する

3.3) プール外の迷子のエラーコードが、CS をランダムに取得または解放しているアプリケーションをクラッシュできないようにする

4) TCriticalSection オブジェクトでの獲得/解放の二重呼び出しは、The_Fox によって指摘された TCriticalSection ドキュメントの単一のメモからの影響にすべての賭けを置きます。「Release への各呼び出しは、Acquire への以前の呼び出しによってバランスを取る必要があります」 http://docwiki.embarcadero.com/Libraries/en/System.SyncObjs.TCriticalSection.Release

そして、今日と明日の他のすべての Pascal 実装がそれを見逃さないことを願っています。

それは壊れやすい練習です。マルチスレッド コードは、クライアント サイトで問題が発生したときに Heisenbugs を作成することで有名ですが、社内で再現して見つけることはできません。将来、あなたの会社が別のプラットフォームや別の言語の実装に拡大する場合、それは潜在的な地雷になります。そして、社内でのテストでは見つけるのが難しい種類の鉱山です。マルチスレッドコードは、過度に防御的になり、不確実性が発生するのを許さない場所です。

于 2012-08-30T15:08:16.813 に答える