I have a problem.
I have this thread pool, which needs to be enhanced a little. And I don’t know how to do it.
Well, actually I know. I have at least three approaches. I just can’t tell which one is the best :(
I’m talking about the OmniThreadLibrary – I know you guessed that already. The problem is the thread pool in OTL doesn’t allow for per-thread resource initialization and that’s something that you need when you’re implementing a connection pool (more background info here). I started adding this functionality but soon found out that I don’t have a good idea on how to implement it. Oh, I have few ideas, they are just not very good :(
At the moment, OTL thread pool functionality is exposed through an interface. This interface is pretty high-level and doesn’t allow the programmer to mess with the underlying thread management. All thread information is hidden in the implementation section. There’s a notification event that’s triggered when pool thread is created or destroyed, but it is only a notification and is triggered asynchronously (and possibly with a delay).
TOTPWorkerThread = class(TThread)
TOmniThreadPool = class(TInterfacedObject, IOmniThreadPool)
The first idea was to add OnThreadInitialization/OnThreadCleanup to the IOmniThreadPool. [Actually something similar already exists - OnWorkerThreadCreated_Asy and OnWorkerThreadDestroyed_Asy – but those events are part of the previous implementation and will be removed very soon.] Those two events would receive a TThread parameter and do the proper initialization there.
There are some big problems though. Let’s say you’ll be implementing database connection pool. You’ll have to open a database connection in OnThreadInitialization. Where would you store that info then? In an external structure, indexed by the TThread? Ugly! Even worse – how would you access the database info from the task that will be executing in the thread pool? By accessing that same structure? Eugh!
A better idea is to implement a subclassed thread class in your own code and then tell the thread pool to use this thread class when creating new thread object. You’d then manage database connection in overridden Initialize/Cleanup methods.
TDBConnectionPoolThread = class(TOTPWorkerThread)
function Initialize: boolean; override;
procedure Cleanup; override;
GlobalOmniThreadPool.ThreadClass := TDBConnectionPoolThread;
Looks much better but there’s again a problem – I’d have to expose TOTPWorkerThread object in the interface section and that’s just plain ugly. Worker thread mechanism should be hidden. Only few people would ever be interested in it.
Thread data subclassing
An even better idea is to add an empty
TOTPWorkerThreadData = class
definition to the interface section of the thread pool unit. IOmniThreadPool would contain a property ThreadDataClass which would point to this definition. And each worker thread would create/destroy an instance of this class in its Execute method.
You’d add database management as
TDBConnectionPoolThreadData = class(TOTPWorkerThreadData)
destructor Destroy; override;
GlobalOmniThreadPool.ThreadDataClass := TDBConnectionPoolThreadData;
[Maybe the constructor has to be virtual here? I never know until I try.]
There’s still a question of accessing this information from the task (and it goes the same for the previous attempt – I just skipped the issue then). I’d have to extend the IOmniTask interface with a method to access per-thread data.
Thread data with interfaces
While writing this mind dump a new idea crossed my mind – what if thread data would be implemented as an interface, without the need for subclassing the thread or thread data? In a way it is a first idea just reimplemented to remove all its problems.
Task interface would be extended with thread data access definitions, approximately like this:
IOtlThreadData = interface
IOtlTask = interface
property ThreadData: IOtlThreadData;
Thread pool would get a property containing a factory method.
TCreateThreadDataProc = function: IOtlThreadData;
IOmniThreadPool = interface
property ThreadDataFactory: TCreateThreadDataProc;
This factory method would be called when thread is created to initialize thread data. Each task would get assigned that same interface into its ThreadData property just before starting its execution in a selected thread. Task would then access ThreadData property to retrieve this information.
In the database connection pool scenario, you’d have to write a connection interface, object and factory.
IDBConnectionPoolThreadData = interface(IOtlThreadData)
property ConnectionInfo: TDBConnectionInfo read GetConnectionInfo;
TDBConnectionPoolThreadData = class(TInterfacedObject, IDBConnectionPoolThreadData )
destructor Destroy; override;
function CreateConnectionPoolThreadData: IDBCOnnectionPoolThreadData;
Result := TDBConnectionPoolThreadData.Create;
GlobalThreadPool.ThreadDataFactory := CreateConnectionPoolThreadData;
This approach requires slightly more work from the programmer but I like it most as it somehow seems the cleanest of them all (plus it is implemented with interfaces which is pretty much the approach used in all OTL code).
So, dear reader, what do you think? If you have better idea, or see a big problem with any of those implementations that I didn’t think of, please do tell in the comments!