ThreadPool for Free Pascal — cheat sheet
Quick reference for v0.9.1. For full contracts, use the Simple API or Producer-Consumer API. Task handles, batches, ranges, and cancellation are covered by the Tasks API.
Choose a pool#
| Need | Use |
|---|---|
| Straightforward fire-and-forget work and an unbounded FIFO | ThreadPool.Simple |
| A ready-to-use process-wide instance | GlobalThreadPool from ThreadPool.Simple |
| Bounded memory and explicit queue saturation handling | ThreadPool.ProducerConsumer |
| Producer backpressure with a submission deadline | TProducerConsumerThreadPool.TryQueue |
| Inspect bounded queue usage | QueueCount, QueueCapacity, and QueueLoadFactor |
| Observe or cancel one pending task | Submit and IThreadPoolTask |
| Coordinate related task handles | IThreadPoolTaskBatch |
| Process an integer range efficiently | SubmitRange |
Import correctly#
On Linux and macOS, cthreads must be the first unit in the program's uses clause. Windows does not need it.
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
ThreadPool.Tasks,
ThreadPool.Simple; // or ThreadPool.ProducerConsumerCreate a pool#
// Managed singleton; do not free it yourself.
GlobalThreadPool.Queue(@DoWork);
// Private unbounded pool.
SimplePool := TSimpleThreadPool.Create(4);
// Private bounded pool: worker count, queue capacity.
BoundedPool := TProducerConsumerThreadPool.Create(4, 1024);Pass 0 as the worker count to use TThread.ProcessorCount. The library always creates at least four workers; positive requests are capped at 2 * TThread.ProcessorCount before that minimum is applied.
Queue work#
| Task form | Call |
|---|---|
| Procedure | Pool.Queue(@DoWork) |
| Object method | Pool.Queue(@Worker.DoWork) |
| Indexed procedure | Pool.Queue(@ProcessItem, Index) |
| Indexed object method | Pool.Queue(@Worker.ProcessItem, Index) |
The indexed callback signatures are:
procedure ProcessItem(Index: Integer);
procedure TWorker.ProcessItem(Index: Integer);Submit observable work#
Task := Pool.Submit(@DoWork);
if not Task.WaitFor(250) then
WriteLn('Still pending or running')
else if Task.State = ttsFailed then
WriteLn(Task.ErrorMessage);States are ttsPending, ttsRunning, ttsCompleted, ttsFailed, and ttsCancelled. WaitFor returns True for any terminal state.
if Task.Cancel then
WriteLn('Cancelled before worker start');Cancellation never interrupts running code. A cancelled entry may remain in the queue as a skipped tombstone until a worker reaches it.
Coordinate a batch#
Batch := NewThreadPoolTaskBatch;
for I := 0 to High(Items) do
Batch.Add(Pool.Submit(@ProcessItem, I));
Batch.WaitFor;
WriteLn(Batch.FinishedCount, '/', Batch.Count);Batch.CancelPending returns the number of pending tasks it cancelled. A wait uses the batch entries present when the call starts and one overall timeout.
Submit a chunked range#
Batch := Pool.SubmitRange(@ProcessItem, 0, High(Items));
Batch.WaitFor;Bounds are inclusive. The default chunk size 0 creates at most four chunks per worker. Pass a positive size for explicit chunks; smaller chunks improve load balance and cancellation granularity but add queue overhead.
Handle bounded-queue saturation#
Prefer TryQueue when using the Producer-Consumer pool:
if not Pool.TryQueue(@DoWork, 50) then
WriteLn('Queue remained full for 50 ms');Queue(...) remains available, but it uses a bounded compatibility wait and raises EQueueFullException if that wait expires. The Simple pool is unbounded, so its TryQueue timeout exists for API symmetry and capacity does not time out.
Wait and shut down#
Pool.WaitForAll; // wait indefinitely
if not Pool.WaitForAll(250) then
WriteLn('Work remains after 250 ms');
Pool.Shutdown; // stop admission, drain, join workersTimeouts are milliseconds:
| Value | Meaning |
|---|---|
0 | Immediate attempt or state check |
| finite value | Maximum wait from call entry |
THREADPOOL_INFINITE | No deadline |
After shutdown begins, Queue and TryQueue raise EThreadPoolShutdown. Repeated Shutdown calls are safe. Do not call Shutdown from one of the pool's own worker tasks.
Read task errors#
Worker exceptions are captured; they do not terminate the pool.
var
MessageText: string;
begin
Pool.ClearErrors;
Pool.Queue(@RiskyWork);
Pool.WaitForAll;
for MessageText in Pool.Errors do
WriteLn(MessageText);
end;LastErroris only the most recent message.Errorsis an oldest-first snapshot, capped atMAX_STORED_ERRORS = 1000.ErrorCountreports the stored count.ClearErrorsclears both the collection andLastError.OnErrorruns synchronously on the worker that caught the exception; keep the handler short, bounded, and thread-safe.
Keep objects alive#
Worker := TWorker.Create;
try
Pool.Queue(@Worker.DoWork);
Pool.WaitForAll; // wait before freeing the callback target
finally
Worker.Free;
end;Also remember:
- Do not free
GlobalThreadPool; its unit owns it. WaitForAllis not an admission barrier for unrelated producers. Coordinate producers first, or useShutdownto close admission before draining.- Tasks and
OnErrorcallbacks have no automatic execution deadline. Add cancellation or application-level timeouts to operations that may block.
Build and test#
lazbuild package/lazarus/threadpool_fp.lpk
lazbuild tests/TestRunner.lpi
./tests/TestRunner -a -p --format=plainOn Windows, run tests/TestRunner.exe in the final command.