3

I came across this while looking for a database connection pool implementation for Delphi.

An object pool needs two methods:

  • get - to acquire an object from the pool (this will create a new instance if the pool is empty or its size has not reached its maximum size), this methods must be thread safe so that one object can not be acquired by two threads at the same time. If all objects are iin use, the get method must block (maybe with an optional time out)

  • put - to release (return) an object to the pool

So a use case would look like

O := Pool.Get;
try
  ... use O
finally
  Pool.Put(O);
end;

Update: added Delphi 2009 tag so Generics.Collections and TMonitor could be part of the implementation

mjn
  • 36,362
  • 28
  • 176
  • 378
  • generic means with generics? – Sir Rufo May 06 '13 at 18:03
  • Which version of Delphi are you targeting? – Arnaud Bouchez May 06 '13 at 18:20
  • @ArnaudBouchez added Delphi 2009 tag (Generics don't work well in this version but this might be a different topic later) – mjn May 06 '13 at 18:30
  • @SirRufo yes Generics definitely can make it more attractive – mjn May 06 '13 at 18:31
  • See [`Pool of Objects - Synchronize - Delphi`](http://stackoverflow.com/a/12201683/576719). – LU RD May 06 '13 at 19:08
  • A thread safe queue can also be used. You will have to wrap a counter around it to ensure that a certain amount of objects can be created in the first place. When an acquired object is retracted from the queue, only one can work on it. When work is finished, push the object back into the queue. If enough objects are created and the queue is empty, the next thread trying to pop an object will be blocked. In XE2, a `TThreadedQueue` will do the job. Also the queue can be preallocated with objects at startup. – LU RD May 06 '13 at 19:31

5 Answers5

2

TMonitor is badly broken in Delphi-2009. It became functional in Delphi-XE2 upd 4, which the answer here is based on (or newer).

Here, the object pool is based on a thread-safe TThreadedQueue.

A mechanism for creating pooled objects is built in with thread safety. Getting an object from the pool is thread-safe and a timeout is defined at pool creation. The queue size is also defined at pool creation, where a callback routine for object creation also is passed.

uses
  System.Classes,Generics.Collections,System.SyncObjs,System.Diagnostics;

type
  TObjectConstructor = function : TObject;

  TMyPool = Class
  private
    FQueueSize,FAllocatedObjects : integer;
    FGetTimeOut : Integer;
    FQueue : TThreadedQueue<TObject>;
    FObjectConstructor : TObjectConstructor;
    FCS : TCriticalSection;
    function AllocateNewObject : TObject;
  public
    Constructor Create( AnObjectConstructor : TObjectConstructor;
                        QueueSize           : Integer;
                        GetTimeOut          : Integer);
    Destructor Destroy; override;
    procedure Put( const AnObject : TObject);
    function Get( var AnObject : TObject) : TWaitResult;
  End;

function TMyPool.AllocateNewObject: TObject;
begin
  FCS.Enter;
  Try
    if Assigned(FObjectConstructor) and
       (FAllocatedObjects < FQueueSize)
    then
    begin
      Inc(FAllocatedObjects);
      Result := FObjectConstructor;
    end
    else
      Result := Nil;
  Finally
    FCS.Leave;
  End;
end;

constructor TMyPool.Create( AnObjectConstructor : TObjectConstructor;
                            QueueSize           : Integer;
                            GetTimeOut          : Integer);
begin
  Inherited Create;

  FCS := TCriticalSection.Create;
  FAllocatedObjects := 0;
  FQueueSize := QueueSize;
  FObjectConstructor := AnObjectConstructor;
  FGetTimeOut := GetTimeOut;
  FQueue := TThreadedQueue<TObject>.Create(FQueueSize+1,Infinite,10);
  // Adding an extra position in queue to safely remove all items on destroy
end;

destructor TMyPool.Destroy;
var
  AQueueSize : integer;
  AnObject : TObject;
  wr : TWaitResult;
begin
  FQueue.PushItem(Nil); // Just to make sure we have an item in queue
  repeat // Free objects in queue
    AnObject := nil;
    wr := FQueue.PopItem(AQueueSize,AnObject);
    if (wr = wrSignaled) then
      AnObject.Free;
  until (AQueueSize = 0);
  FQueue.Free;
  FCS.Free;

  Inherited;
end;

function TMyPool.Get(var AnObject: TObject) : TWaitResult;
var
  sw : TStopWatch;
begin
  AnObject := nil;
  // If queue is empty, and not filled with enough objects, create a new.
  sw := TStopWatch.Create;
  repeat
    sw.Start;
    Result := FQueue.PopItem( AnObject); // Timeout = 10 ms
    if (Result = wrTimeOut) and
       (FAllocatedObjects < FQueueSize) and
       Assigned(FObjectConstructor)
    then begin  // See if a new object can be allocated
      AnObject := Self.AllocateNewObject;
      if Assigned(AnObject) then
      begin
        Result := wrSignaled;
        Exit;
      end;
    end;
    sw.Stop;
  until (Result = wrSignaled) or (sw.ElapsedMilliseconds > FGetTimeOut);
end;

procedure TMyPool.Put( const AnObject: TObject);
begin
  FQueue.PushItem(AnObject); // Put object back into queue
end;

Define your TObjectConstructor function like this:

function MyObjectConstructor : TObject;
begin
  Result := TMyObject.Create( {Some optional parameters});
end;

And an example how to use:

var
  AnObject : TObject;
  MyObject : TMyObject;
  wr : TWaitResult;
begin
  wr := MyObjPool.Get(AnObject);
  if (wr = wrSignaled) then 
  begin
    MyObject := TMyObject(AnObject);
    try
      // Do something with MyObject
    finally
      MyObjPool.Put(AnObject);
    end;
  end;
end
LU RD
  • 34,438
  • 5
  • 88
  • 296
  • There are patches available for D2009 and XE TMonitor (see http://www.thedelphigeek.com/2011/05/tmonitor-bug.html) – mjn May 07 '13 at 05:38
  • Be aware that there are two errors in TMonitor. One that shows its ugly face when multiple consumers hit an empty queue. I think this is what the patches shown at @gabr corrects. The other error manifests when multiple consumers hits a full queue. See [`TThreadedQueue not capable of multiple consumers?`](http://stackoverflow.com/q/4856306/576719) for a test of these conditions. I used a slightly modified queue shown [`here`](http://www.pascalgamedevelopment.com/showthread.php?4961-freepascal-Delphi-thread-safe-queue) instead of TThreadedQueue until XE2 upd 4. – LU RD May 07 '13 at 06:32
  • many thanks for the pointer to the second severe problem in TMonitor. At least it works with one consumer in Delphi 2009, better than nothing ;) – mjn May 07 '13 at 07:33
0

Depending on which (threading) platform or architecture you use to perform tasks or jobs on several threads, a 'generic' way to handle database connections is to use threadvar and a database connection per thread. If you have a thread pool or thread manager, it should be extended to start the DB connection when adding a thread (or connect to the DB on the first task run on a thread), and to close the database connection when a thread is destroyed.

Stijn Sanders
  • 35,982
  • 11
  • 45
  • 67
0

No there is no generic object pool in Delphi. You will have to roll your own, or use third party code like e.g. here: delphipooling

iamjoosy
  • 3,299
  • 20
  • 30
0

Just came across Boosting Work Classes with a mini Object Pool today, by Eric, the current awesome developer of dwScript.

Edwin Yip
  • 4,089
  • 4
  • 40
  • 86
0

Spring4D - Spring.Container.Pool.pas has an object pool implementation, I haven't tried it, but you know, people of the Delphi community know that Spring4D is of high quality :)

There doesn't seem to have a document, but it has test cases here

Edwin Yip
  • 4,089
  • 4
  • 40
  • 86