A lightweight, dependency-free thread pool library for Free Pascal. It provides an unbounded pool for straightforward parallel work and a bounded pool for producer-consumer workloads that need backpressure.
Quick start · Cheat sheet · API documentation · Examples · v0.8.5 release notes
Tip
✨ New in v0.8.5: refreshed project identity, a shorter README, and a new cheat sheet. Runtime behavior is unchanged from v0.8.0.
Note
This library is designed for simple parallel processing and learning-friendly integration. It is not intended to replace high-load, production-scale frameworks such as mORMot2, ezthreads, or OmniThreadLibrary.
ThreadPool.Simple |
ThreadPool.ProducerConsumer |
|
|---|---|---|
| Queue | Dynamically growing FIFO | Fixed-size circular FIFO |
| Capacity | Unbounded | Configurable; 1024 by default |
| Submission | Immediate | Timeout-aware backpressure |
| Convenience | Managed GlobalThreadPool |
Create a pool instance |
| Best for | Predictable, moderate fire-and-forget work | Producers that may outpace consumers |
Both implementations provide:
- event-driven workers with no polling sleeps;
- four task forms: procedures, methods, and indexed variants;
- timeout-aware
TryQueueandWaitForAlloverloads; - deterministic, draining
Shutdown; - captured worker exceptions through
LastError,Errors, andOnError; and - automatic worker-count selection with safety limits.
Important
🧵 Unix thread setup: On Linux and macOS, cthreads must be the first unit
in the program's uses clause. Without it, Free Pascal can compile
successfully but fail at runtime when the pool creates worker threads. Windows
does not need cthreads.
Use the managed global pool when you only need to submit work and wait for it:
program SimplePoolDemo;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
ThreadPool.Simple;
procedure ProcessItem(Index: Integer);
begin
WriteLn('Processing item ', Index);
end;
var
I: Integer;
begin
for I := 1 to 5 do
GlobalThreadPool.Queue(@ProcessItem, I);
GlobalThreadPool.WaitForAll;
end.GlobalThreadPool is created and destroyed by the unit. Do not free it
yourself.
Use a bounded pool when submission may need to wait for queue space:
program BoundedPoolDemo;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
ThreadPool.ProducerConsumer;
procedure DoWork;
begin
WriteLn('Working');
end;
var
Pool: TProducerConsumerThreadPool;
begin
Pool := TProducerConsumerThreadPool.Create(0, 1024);
try
if not Pool.TryQueue(@DoWork, 50) then
WriteLn('Queue remained full for 50 ms');
Pool.Shutdown; // close admission, drain accepted work, join workers
finally
Pool.Free;
end;
end.The first constructor argument is the worker count; 0 selects
TThread.ProcessorCount. The second is queue capacity.
Both pools follow one monotonic lifecycle:
tpsAccepting -> tpsDraining -> tpsStopped
Shutdown stops new admission, waits for submissions already entering the
pool, drains every accepted task, wakes workers, and joins them. It is safe to
call more than once. Queueing after shutdown begins raises
EThreadPoolShutdown.
Pool.WaitForAll; // wait indefinitely
if not Pool.WaitForAll(250) then // milliseconds
WriteLn('Work remains');
Pool.Shutdown;Timeout values use these rules:
| Value | Meaning |
|---|---|
0 |
Immediate attempt or check |
| finite value | Maximum wait from call entry, in milliseconds |
THREADPOOL_INFINITE |
No deadline |
The Simple queue is unbounded, so its TryQueue timeout is present for API
symmetry and capacity cannot time out. For the bounded pool, TryQueue returns
False if no slot becomes available before the deadline. The legacy Queue
calls use a bounded compatibility wait and raise EQueueFullException when it
expires.
Warning
⏱️ Coordination matters: WaitForAll does not stop unrelated producer
threads from submitting more work. Coordinate producers first, or call
Shutdown to close admission before draining. Tasks and OnError callbacks
also have no automatic execution deadline; add cancellation or
application-level timeouts where needed.
Task exceptions are caught so a worker failure does 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 the most recent message.Errorsis an oldest-first snapshot capped at 1000 entries.ErrorCountreports the stored count.ClearErrorsclears the collection andLastError.OnErrorfires synchronously on the worker that caught the exception. Keep callbacks short, bounded, and thread-safe.
| Form | Example |
|---|---|
| Procedure | Pool.Queue(@DoWork) |
| Object method | Pool.Queue(@Worker.DoWork) |
| Indexed procedure | Pool.Queue(@ProcessItem, I) |
| Indexed object method | Pool.Queue(@Worker.ProcessItem, I) |
Objects must remain alive until all queued methods that reference them have
finished. Call WaitForAll before freeing a callback target.
The library has no external dependencies.
- Add
srcto the project's unit search path, or installpackage/lazarus/threadpool_fp.lpkin Lazarus. - Add
ThreadPool.SimpleorThreadPool.ProducerConsumerto theusesclause. - On Unix-like systems, put
cthreadsfirst as shown in the quick start.
Requirements:
- Free Pascal 3.2.2 or later
- Lazarus 4.0 or later when using the package or project files
| Start with | Demonstrates |
|---|---|
Starter |
Smallest compilable program with explanatory comments |
SimpleDemo |
Procedures, methods, indexes, and the global pool |
ProdConSimpleDemo |
Basic bounded-pool ownership and queueing |
SimpleErrorHandlingBasic |
Reading captured errors after completion |
More focused samples cover:
- computation:
SimpleSquareNumbersandProdConSquareNumbers; - stateful processing:
SimpleThreadpoolDemo,SimpleWordCounter, andProdConMessageProcessor; - advanced callbacks:
SimpleErrorHandling; - real I/O:
ParallelFileHasherandParallelUrlFetcher.
| Document | Purpose |
|---|---|
| Cheat sheet | Calls and safety rules at a glance |
| Simple API | Complete unbounded-pool reference |
| Producer-Consumer API | Complete bounded-pool reference |
| Simple technical guide | Internal design and synchronization |
| Producer-Consumer technical guide | Queue and backpressure internals |
| v0.8.5 release notes | Current release scope and compatibility |
| Changelog | Full version history |
The banner's editable source is
docs/assets/threadpool-banner.svg; the
README displays its synchronized PNG render.
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. CI builds the
package, tests, benchmark, and every example on Windows and Linux.
Bug reports, documentation improvements, examples, and code contributions are welcome. See CONTRIBUTING.md for the build, style, and pull request workflow.
ThreadPool for Free Pascal is available under the MIT License.
