Simple Thread Pool
ThreadPool.Simple provides the unbounded, dynamically-growing FIFO pool and is the default starting point for most programs.
It exposes two uses: the process-wide GlobalThreadPool and private TSimpleThreadPool instances.
Using GlobalThreadPool#
GlobalThreadPool is created when the unit initializes and freed when your program exits. It needs no construction and no cleanup.
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
ThreadPool.Simple;
GlobalThreadPool.Queue(@DoWork);
GlobalThreadPool.WaitForAll;- Do not call
GlobalThreadPool.Free; the unit manages its lifetime. - Its worker count is
TThread.ProcessorCount, minimum four. - It stays alive for the whole process, so it is safe to use from any unit.
If your program uses several pools, GlobalThreadPool is still useful as the process-wide default and for fire-and-forget work.
Private pools#
Create a private pool when you need a specific worker count or a lifetime you control:
var
Pool: TSimpleThreadPool;
begin
Pool := TSimpleThreadPool.Create(4);
try
for I := 0 to High(Items) do
Pool.Queue(@ProcessItem, I);
Pool.WaitForAll;
finally
Pool.Free;
end;
end;Pool.Free calls Shutdown, which drains accepted work and joins the worker threads; a finally destructor is still the correct pattern because it runs after your WaitForAll point.
Queue vs Submit#
Queue is the lightest path: fire-and-forget, no handle. Submit returns an IThreadPoolTask you can wait on, inspect, or cancel while pending.
// Fire-and-forget.
Pool.Queue(@DoWork);
// Observable.
Task := Pool.Submit(@DoWork);
if Task.WaitFor(250) and (Task.State = ttsFailed) then
WriteLn(Task.ErrorMessage);Queue callers who never need a handle do not pay for task state or completion events.
Workers and order#
- Worker count is fixed at construction:
0selectsProcessorCount, the minimum is four, and requests above2 × ProcessorCountare capped. - The queue is FIFO at the moment of enqueue, but callbacks run concurrently, so their completion order is unspecified.
- Idle workers block without polling and are woken on submission.
Waiting and readiness#
WaitForAll blocks until every accepted callback has finished. Reading shared results, checking errors, or freeing callback targets must happen after this point.
if not Pool.WaitForAll(250) then
WriteLn('Work remains after 250 ms');The queue is unbounded#
TryQueue(..., TimeoutMS) exists for API symmetry with the bounded pool; its timeout normally never expires because the Simple queue grows to fit. Capacity is limited only by available memory. If a runaway producer matters to you, consider the bounded pool instead (Backpressure).
Errors#
Worker exceptions are captured in LastError and Errors; they do not stop the pool. See Error Handling.
Concurrency contracts#
- All
Queue,TryQueue, andSubmitoverloads are thread-safe and may be called from any thread. - Queueing from inside a callback is allowed on the Simple pool: the queue never blocks, so a worker cannot deadlock itself while submitting.
Shutdownmust not be called from one of the pool's own workers.- See Thread Safety for the full list.