Skip to content

ikelaiah/threadpool-fp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

100 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ThreadPool for Free Pascal — concurrent task queue splitting across parallel worker threads

ThreadPool for Free Pascal

Version License: MIT Free Pascal Lazarus CI Windows Linux No dependencies

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.

Choose a pool

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 TryQueue and WaitForAll overloads;
  • deterministic, draining Shutdown;
  • captured worker exceptions through LastError, Errors, and OnError; and
  • automatic worker-count selection with safety limits.

Quick start

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.

Simple pool

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.

Producer-consumer pool

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.

Lifecycle and timeouts

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.

Error handling

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;
  • LastError is the most recent message.
  • Errors is an oldest-first snapshot capped at 1000 entries.
  • ErrorCount reports the stored count.
  • ClearErrors clears the collection and LastError.
  • OnError fires synchronously on the worker that caught the exception. Keep callbacks short, bounded, and thread-safe.

Supported task forms

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.

Installation

The library has no external dependencies.

  1. Add src to the project's unit search path, or install package/lazarus/threadpool_fp.lpk in Lazarus.
  2. Add ThreadPool.Simple or ThreadPool.ProducerConsumer to the uses clause.
  3. On Unix-like systems, put cthreads first 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

Examples

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:

Documentation

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.

Build and test

lazbuild package/lazarus/threadpool_fp.lpk
lazbuild tests/TestRunner.lpi
./tests/TestRunner -a -p --format=plain

On Windows, run tests/TestRunner.exe in the final command. CI builds the package, tests, benchmark, and every example on Windows and Linux.

Contributing

Bug reports, documentation improvements, examples, and code contributions are welcome. See CONTRIBUTING.md for the build, style, and pull request workflow.

License

ThreadPool for Free Pascal is available under the MIT License.

About

A lightweight, easy-to-use thread pool implementation for Free Pascal. Simplify parallel processing for simple tasks! ⚡

Topics

Resources

License

Contributing

Stars

20 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors

Languages