Skip to content

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

License

Notifications You must be signed in to change notification settings

ikelaiah/threadpool-fp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🚀 ThreadPool for Free Pascal

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

Important

Parallel processing can improve performance for CPU-intensive tasks that can be executed independently. However, not all tasks benefit from parallelization. See Thread Management for important considerations.

Note

This library is an experimental project, as I was exploring the concept of thread pools and how to implement them in Free Pascal.

Hence, this library is not suitable for high-load applications. It is designed for simple parallel processing tasks that can be executed in parallel.

Tip

If you are looking for performant and battle-tested threading libraries, please check out these alternatives:

📑 Table of Contents

✨ Features

This library provides two thread pool implementations, each with its own strengths:

ThreadPool Implementations

1. Simple Thread Pool (ThreadPool.Simple)

uses ThreadPool.Simple;
  • Global singleton instance for quick use
  • Direct task execution
  • Automatic thread count management
  • Best for simple parallel tasks
  • Lower memory overhead

2. Producer-Consumer Thread Pool (ThreadPool.ProducerConsumer)

uses ThreadPool.ProducerConsumer;

A thread pool with fixed-size circular buffer (1024 items) and built-in backpressure handling:

  • Queue Management

    • Fixed-size circular buffer for predictable memory usage
    • Efficient space reuse without dynamic resizing
    • Configurable capacity (default: 1024 items)
  • Backpressure Handling

    • Load-based adaptive delays (10ms to 100ms)
    • Automatic retry mechanism (up to 5 attempts)
    • Throws EQueueFullException when retries exhausted
  • Monitoring & Debug

    • Thread-safe error capture with thread IDs
    • Detailed debug logging (can be disabled)

Warning

While the system includes automatic retry mechanisms, it's recommended that users implement their own error handling strategies for scenarios where the queue remains full after all retry attempts.

Shared Features

  • Thread Count Management

    • Minimum 4 threads for optimal parallelism
    • Maximum 2× ProcessorCount to prevent overload
    • Fixed count after initialization
  • Task Types Support

    • Simple procedures: Pool.Queue(@MyProc)
    • Object methods: Pool.Queue(@MyObject.MyMethod)
    • Indexed variants: Pool.Queue(@MyProc, Index)
  • Thread Safety

    • Built-in synchronization
    • Safe resource sharing
    • Protected error handling
  • Error Management

    • Thread-specific error capture
    • Error messages with thread IDs
    • Continuous operation after exceptions

Note

Thread count is determined by TThread.ProcessorCount at startup and remains fixed. See Thread Management for details.

🏃 Quick Start

Simple Thread Pool

uses ThreadPool.Simple;

// Simple parallel processing
procedure ProcessItem(index: Integer);
begin
  WriteLn('Processing item: ', index);
end;

begin
  // Queue multiple items
  for i := 1 to 5 do
    GlobalThreadPool.Queue(@ProcessItem, i);
    
  GlobalThreadPool.WaitForAll;
end;

Producer-Consumer Thread Pool

uses ThreadPool.ProducerConsumer;

procedure DoWork;
begin
  WriteLn('Working in thread: ', GetCurrentThreadId);
end;

var
  Pool: TProducerConsumerThreadPool;
begin
  Pool := TProducerConsumerThreadPool.Create;
  try
    Pool.Queue(@DoWork);
    Pool.WaitForAll;
  finally
    Pool.Free;
  end;
end;

Error Handling Simple Thread Pool

program ErrorHandling;

{$mode objfpc}{$H+}{$J-}

uses
  Classes, SysUtils, ThreadPool.Simple;

procedure RiskyProcedure;
begin
  raise Exception.Create('Something went wrong!');
end;

var
  Pool: TSimpleThreadPool;
begin
  Pool := TSimpleThreadPool.Create(4); // Create with 4 threads
  try
    Pool.Queue(@RiskyProcedure);
    Pool.WaitForAll;
    
    // Check for errors after completion
    if Pool.LastError <> '' then
    begin
      WriteLn('An error occurred: ', Pool.LastError);
      Pool.ClearLastError;  // Clear for reuse if needed
    end;
  finally
    Pool.Free;
  end;
end.

Error Handling Producer-Consumer Thread Pool

program ErrorHandling;

{$mode objfpc}{$H+}{$J-}

uses
  Classes, SysUtils, ThreadPool.ProducerConsumer;

procedure RiskyProcedure;
begin
  raise Exception.Create('Something went wrong!');
end;

var
  Pool: TProducerConsumerThreadPool;
begin
  Pool := TProducerConsumerThreadPool.Create;
  try
    try
      Pool.Queue(@RiskyProcedure);
    except
      on E: EQueueFullException do
        WriteLn('Queue is full after retries: ', E.Message);
    end;
    
    Pool.WaitForAll;
    
    // Check for errors after completion
    if Pool.LastError <> '' then
    begin
      WriteLn('An error occurred: ', Pool.LastError);
      Pool.ClearLastError;  // Clear for reuse if needed
    end;
  finally
    Pool.Free;
  end;
end.

Tips

Note

Error Handling

  • 🛡️ Exceptions are caught and stored with thread IDs
  • ⚡ Pool continues operating after exceptions
  • 🔄 Use ClearLastError to reset error state

Debugging

  • 🔍 Error messages contain thread identification
  • 📝 Debug logging enabled by default (configurable)
  • 📊 Queue capacity monitoring available

Use Simple Thread Pool when:

  • Direct task execution without queuing needed
  • Task count is predictable and moderate
  • Low memory overhead is important
  • Global instance (GlobalThreadPool) convenience desired
  • Simple error handling is sufficient

Use Producer-Consumer Pool when:

  • High volume of tasks with rate control needed
  • Backpressure handling required
  • Queue overflow protection important
  • Need detailed execution monitoring
  • Want configurable retry mechanisms

📚 Examples

Simple Thread Pool Examples

  1. 🎓 Simple Demo (examples/SimpleDemo/SimpleDemo.lpr)

    • Basic usage with GlobalThreadPool
    • Demonstrates procedures and methods
    • Shows proper object lifetime
  2. 🔢 Thread Pool Demo (examples/SimpleThreadpoolDemo/SimpleThreadpoolDemo.lpr)

    • Custom thread pool management
    • Thread-safe operations
    • Error handling patterns
  3. 📝 Word Counter (examples/SimpleWordCounter/SimpleWordCounter.lpr)

    • Queue-based task processing
    • Thread-safe counters
    • File I/O with queue management
  4. 🔢 Square Numbers (examples/SimpleSquareNumbers/SimpleSquareNumbers.lpr)

    • High volume task processing
    • Queue full handling
    • Performance comparison

Producer-Consumer Examples

  1. 🎓 Simple Demo (examples/ProdConSimpleDemo/ProdConSimpleDemo.lpr)

    • Basic usage with ProducerConsumerThreadPool
    • Demonstrates procedures and methods
    • Shows proper object lifetime
  2. 🔢 Square Numbers (examples/ProdConSquareNumbers/ProdConSquareNumbers.lpr)

    • High volume task processing
    • Queue full handling
    • Backpressure demonstration
    • Performance monitoring
  3. 📝 Message Processor (examples/ProdConMessageProcessor/ProdConMessageProcessor.lpr)

    • Queue-based task processing
    • Thread-safe message handling
    • Graceful shutdown
    • Error handling patterns

🛠️ Installation

  1. Add the src directory to your project's search path

  2. Choose your implementation:

    For Simple Thread Pool:

    uses ThreadPool.Simple;

    For Producer-Consumer Thread Pool:

    uses ThreadPool.ProducerConsumer;
  3. Start using:

    • Simple: Use GlobalThreadPool or create TSimpleThreadPool
    • Producer-Consumer: Create TProducerConsumerThreadPool

⚙️ Requirements

  • 💻 Free Pascal 3.2.2 or later
  • 📦 Lazarus 3.6.0 or later
  • 🆓 No external dependencies

📚 Documentation

🧪 Testing

  1. Go to the tests/ directory
  2. Open TestRunner.lpi in Lazarus IDE and compile
  3. Run ./TestRunner.exe -a -p --format=plain to see the test results.
  4. Ensure all tests pass to verify the library's functionality

May take up to 5 mins to run all tests.

🧵 Thread Management

Thread Count Rules

  • Default: Uses ProcessorCount when thread count ≤ 0
  • Minimum: 4 threads enforced
  • Maximum: 2× ProcessorCount
  • Fixed after creation (no dynamic scaling)

Implementation Characteristics

Simple Thread Pool

  • Direct task execution without queuing
  • Continuous task processing
  • Clean shutdown handling

Producer-Consumer Thread Pool

  • Fixed-size circular queue (1024 items by default, configurable)
  • Backpressure handling with adaptive delays
  • Graceful overflow management

🚧 Planned/In Progress

  • Adaptive thread adjustment based on a load factor
  • Support for procedure Queue(AMethod: TProc; AArgs: array of Const);
  • More comprehensive tests
  • More examples

👏 Acknowledgments

Special thanks to the Free Pascal and Lazarus communities and the creators of the threading libraries mentioned above for inspiration!

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.


💡 More Tip: Check out the examples directory for more usage patterns!

About

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

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages