Skip to content

ikelaiah/threadsafecollections-fp

Repository files navigation

πŸ”’ ThreadSafeCollections-FP

License: MIT Free Pascal Lazarus Supports Windows Supports Linux Version No Dependencies Documentation Status

A thread-safe generic collections library for Free Pascal, designed for learning and experimentation.

Note

πŸ“š Library Maturity: This is a learning-focused project with stable core functionality.

For production applications requiring battle-tested code, consider these more mature alternatives:

  1. FPC Generics.Collections - Official FPC generic collections
  2. FCL-STL - FPC's template library
  3. LGenerics - Comprehensive generics library

🚧 Development Status

Latest Release: v0.8.1 - Code Maintainability Update

Current State:

  • βœ… Basic operations working (Add, Remove, GetItem)
  • βœ… Thread safety verified through testing
  • βœ… Memory management stable
  • βœ… Thread-Safe Iterator Support
    • Iterators use RAII-style locking through interface counting
    • Thread-safe iteration with automatic lock management
    • Each iterator maintains its own lock token
  • βœ… Bulk operations support
  • βœ… NEW in v0.8.1: Code maintainability improvements
    • Algorithm complexity annotations (Big-O) on all 80+ methods
    • Centralized error messages (14 constants)
    • Named constants replacing magic numbers
    • Zero memory leaks (100% cleanup rate)
  • βœ… v0.8.0: Performance optimisations implemented
    • Circular array-based Deque (5-10x faster)
    • Pre-allocation strategies for List
    • Optimised hash table resizing

Planned Features:

  • πŸ”„ Read-write lock support (concurrent reads)
  • πŸ”„ Lock-free operations for simple checks
  • πŸ”„ More specialized types

🎯 Why Use This?

  • πŸ’‘ Learning Tool: Perfect for understanding thread-safe collections
  • πŸ”’ Simple Thread Safety: Just like regular collections, but thread-safe
  • πŸš€ Easy to Use: Specialized types for common data (Integer, String, Boolean, Real)
  • ⚑ Good for Prototypes: Ideal for quick multi-threaded demos

πŸŽ“ Getting Started

This library provides four main collection types:

  1. ThreadSafeList: Like an array that can grow
uses 
  ThreadSafeCollections.List;  // Built-in comparers included!

var
  List: specialize TThreadSafeList<Integer>;
begin
  // Two ways to create a list:
  
  // 1. Basic creation (using built-in comparers)
  List := specialize TThreadSafeList<Integer>.Create(@IntegerComparer);    // For integers
  // List := specialize TThreadSafeList<string>.Create(@StringComparer);   // For strings
  // List := specialize TThreadSafeList<Boolean>.Create(@BooleanComparer); // For booleans
  // List := specialize TThreadSafeList<Real>.Create(@RealComparer);       // For reals
  
  // 2. With initial capacity (for better performance)
  List := specialize TThreadSafeList<Integer>.Create(1000, @IntegerComparer);
  
  try
    List.Add(42);  // Simple to use!
    List.Sort;     // Automatic sorting with the comparer
  finally
    List.Free;
  end;
end;

Tip

Built-in comparers in ThreadSafeCollections.List:

  • IntegerComparer: For Integer types
  • StringComparer: For string types
  • BooleanComparer: For Boolean types
  • RealComparer: For Real types

For custom types, implement your own comparer: function MyComparer(const A, B: TMyType): Integer;

  1. ThreadSafeDeque: A double-ended queue (v0.8: Now circular array-based!)
var
  Deque: specialize TThreadSafeDeque<Integer>;
begin
  // Create with default capacity (16) or specify initial capacity
  Deque := specialize TThreadSafeDeque<Integer>.Create;
  // Deque := specialize TThreadSafeDeque<Integer>.Create(1000); // For better performance
  try
    Deque.PushBack(1);
    Deque.PushFront(2);
    WriteLn('Front item: ', Deque.PopFront);
    WriteLn('Back item: ', Deque.PopBack);
  finally
    Deque.Free;
  end;
end;
  1. ThreadSafeDictionary: Store key-value pairs
uses 
  ThreadSafeCollections.Dictionary;

var
  Dict: specialize TThreadSafeDictionary<string, integer>;
begin
  Dict := specialize TThreadSafeDictionary<string, integer>.Create;
  try
    Dict.Add('one', 1);
    Dict.Add('two', 2);
    
    if Dict.Contains('one') then
      WriteLn('Found: ', Dict['one']);
  finally
    Dict.Free;
  end;
end;

Tip

  • For basic types (integer, string, etc.), use Create or Create(capacity)
  • For custom types, use Create(hashFunc, equalityFunc) or Create(capacity, hashFunc, equalityFunc)
  1. ThreadSafeHashSet: Store unique values
var
  UniqueNames: TThreadSafeHashSetString;
begin
  UniqueNames := TThreadSafeHashSetString.Create;
  try
    UniqueNames.Add('unique');  // Duplicates handled automatically
  finally
    UniqueNames.Free;
  end;
end;

Tip

Always use try-finally blocks to ensure proper cleanup:

try
  // Your code here
finally
  Collection.Free;
end;

πŸš€ Quick Start

πŸ“‹ Requirements

  • Free Pascal 3.2.2 or later
  • No external dependencies

Using ThreadSafeList

uses ThreadSafeCollections.List;

// Create a thread-safe list of integers
var
  Numbers: specialize TThreadSafeList<Integer>;
begin
  Numbers := specialize TThreadSafeList<Integer>.Create(@IntegerComparer);
  try
    // Multiple threads can safely add/remove items
    Numbers.Add(42);
    Numbers.Add(17);
    Numbers.Sort;  // Thread-safe sorting
    
    WriteLn(Numbers[0]); // Thread-safe access
  finally
    Numbers.Free;
  end;
end;

ThreadSafeList with Custom Types

uses ThreadSafeCollections.List;

type
  TStudent = record
      Name: string;
      StudentId: Integer;
end;

// Custom comparer for sorting
function StudentNameComparer(const A, B: TStudent): Integer;
begin
  Result := CompareStr(A.Name, B.Name);
end;

var
  Students: specialize TThreadSafeList<TStudent>;
begin
  Students := specialize TThreadSafeList<TStudent>.Create(@StudentNameComparer);
  try 
      // ... use the list
  finally
      Students.Free;
  end;
end;

Using ThreadSafeDeque

uses
  ThreadSafeCollections.Deque;

var
  Deque: specialize TThreadSafeDeque<string>;
  Name: string;
begin
  Deque := specialize TThreadSafeDeque<string>.Create;
  try
    // Add items to the front and back
    Deque.PushFront('Obed');
    Deque.PushFront('Jesse');
    Deque.PushBack('David');

    // Remove items from the front and back
    if Deque.TryPopFront(Name) then
      WriteLn('Popped from front: ', Name);

    if Deque.TryPopBack(Name) then
      WriteLn('Popped from back: ', Name);
  finally
    Deque.Free;
  end;

// Other code

end.

Using ThreadSafeDeque with Custom Types

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

uses
  ThreadSafeCollections.Deque;

type
  TPerson = record
    Name: string;
    Age: Integer;
    public
    constructor Create(NewName: string; NewAge: Integer);
  end;

constructor TPerson.Create(NewName: string; NewAge: Integer);
begin
  Name := NewName;
  Age := NewAge;
end;

var
  Deque: specialize TThreadSafeDeque<TPerson>;
  Person: TPerson;
begin
  Deque := specialize TThreadSafeDeque<TPerson>.Create;
  try
    // Add items to the front and back
    Deque.PushFront(TPerson.Create('Alice', 30));
    Deque.PushBack(TPerson.Create('Bob', 25));

    // Remove items from the front and back
    if Deque.TryPopFront(Person) then
      WriteLn('Popped from front: ', Person.Name);

    if Deque.TryPopBack(Person) then
      WriteLn('Popped from back: ', Person.Name);
  finally
    Deque.Free;
  end;
end;

Using ThreadSafeHashSet

1. Basic String Set (Using Built-in Type)

uses 
  ThreadSafeCollections.HashSet;

var
  UniqueNames: TThreadSafeHashSetString;
begin
  UniqueNames := TThreadSafeHashSetString.Create;
  try
    // Add items (duplicates are ignored)
    UniqueNames.Add('Alice');    // Returns True (added)
    UniqueNames.Add('Bob');      // Returns True (added)
    UniqueNames.Add('Alice');    // Returns False (already exists)
    
    // Check existence
    if UniqueNames.Contains('Alice') then
      WriteLn('Alice is in the set');
      
    // Remove items
    UniqueNames.Remove('Bob');   // Returns True (was removed)
    
    WriteLn('Count: ', UniqueNames.Count); // Outputs: 1
  finally
    UniqueNames.Free;
  end;
end;

2. Custom Type Set (Advanced Usage)

uses 
  ThreadSafeCollections.HashSet;

type
  TPoint = record
    X, Y: Integer;
  end;

// Compare two points for equality
function PointEquals(const A, B: TPoint): Boolean;
begin
  Result := (A.X = B.X) and (A.Y = B.Y);
end;

// Generate hash code for a point
function PointHash(const Value: TPoint): Cardinal;
begin
  Result := Cardinal(Value.X xor Value.Y);
end;

var
  UniquePoints: specialize TThreadSafeHashSet<TPoint>;
begin
  UniquePoints := specialize TThreadSafeHashSet<TPoint>.Create(@PointEquals, @PointHash);
  try
    // Add unique points
    UniquePoints.Add(TPoint.Create(1, 1));
    UniquePoints.Add(TPoint.Create(2, 2));
    
    // Check for existence
    var Point := TPoint.Create(1, 1);
    if UniquePoints.Contains(Point) then
      WriteLn('Point (1,1) exists');
  finally
    UniquePoints.Free;
  end;
end;

Using ThreadSafeHashSet with Set Operations

var
  SetA, SetB: TThreadSafeHashSetInteger;
begin
  SetA := TThreadSafeHashSetInteger.Create;
  SetB := TThreadSafeHashSetInteger.Create;
  try
    // Setup sets
    SetA.Add(1);
    SetA.Add(2);
    SetA.Add(3);
    
    SetB.Add(2);
    SetB.Add(3);
    SetB.Add(4);
    
    // Intersection: Keep only items in both sets
    SetA.IntersectWith(SetB);  // SetA now contains {2, 3}
    
    // Union: Add all unique items from both sets
    SetA.UnionWith(SetB);      // SetA now contains {1, 2, 3, 4}
    
    // Difference: Remove items that exist in SetB
    SetA.ExceptWith(SetB);     // SetA now contains {1}
    
    // Bulk operations
    var Numbers: array of Integer;
    SetLength(Numbers, 3);
    Numbers[0] := 5;
    Numbers[1] := 6;
    Numbers[2] := 7;
    
    SetA.AddRange(Numbers);    // Add multiple items at once
    SetA.AddRange(SetB);       // Add all items from another set
  finally
    SetA.Free;
    SetB.Free;
  end;
end;

Performance Characteristics

Performance benchmarks on Dell Inspiron 15 7510 (Intel i7-11800H @ 2.30GHz, 8 cores, 16GB RAM, Windows 11):

List Operations:

Operation Time (ms) Items Notes
Sort Integers < 1 100,000 Quicksort
Sort Strings 94 100,000 Quicksort
Sort Students (Name) 157 100,000 Custom comparer
Sort Students (ID) 94 100,000 Custom comparer

Dictionary Operations:

Operation Time (ms) Items Notes
Add 63 100,000 Bulk insert
Find 265 100,000 Sequential lookups

HashSet Operations:

Operation Time (ms) Items Notes
Add 31 100,000 Bulk insert
Find 47 100,000 Contains checks
Stress Test 172 100,000 Mixed operations
Hash Collisions 3,468 10,000 Forced collisions

Tip

Use bulk operations (AddRange, RemoveRange) for better performance when working with multiple items.

πŸ“₯ Installation

Method 1: Using Git

  1. Clone the repository:

    git clone https://github.com/ikelaiah/ThreadSafeCollections-FP.git
  2. Add to Your Project:

    • In Lazarus IDE:

      1. Project β†’ Project Inspector
      2. Add Unit β†’ Browse to src directory
      3. Select needed units (e.g., ThreadSafeCollections.List.pas)
    • In FPC Project:

      {$UNITPATH your/path/to/ThreadSafeCollections-FP/src}

Method 2: Manual Installation

  1. Download ZIP from GitHub
  2. Extract to your preferred location
  3. Add src directory to your project's search path:
    program YourProject;
    
    {$mode objfpc}{$H+}{$J-}
    {$UNITPATH path/to/ThreadSafeCollections-FP/src}
    
    uses
      ThreadSafeCollections.List,  // For List
      ThreadSafeCollections.Deque, // For Deque
      // ... other units as needed

Verify Installation

Create a simple test program:

program TestInstall;

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

uses
  ThreadSafeCollections.List;

var
  List: specialize TThreadSafeList<Integer>;
begin
  List := specialize TThreadSafeList<Integer>.Create(@IntegerComparer);
  try
    List.Add(42);
    WriteLn('Installation successful!');
  finally
    List.Free;
  end;
end.

Troubleshooting

  1. Compilation Errors:

    • Ensure FPC 3.2.2 or later
    • Check unit path is correct
    • Verify all required files are present
  2. Runtime Errors:

    • Check memory management (use try-finally)
    • Verify comparers are provided where needed

πŸ§ͺ Thread Safety Examples

Safe Iteration

var
  List: specialize TThreadSafeList<Integer>;
  Item: Integer;
begin
  List := specialize TThreadSafeList<Integer>.Create(@IntegerComparer);
  try
    // Iterator automatically acquires lock through RAII
    for Item in List do
    begin
      // Other threads wait until iteration completes
      WriteLn(Item);
    end; // Lock automatically released here
  finally
    List.Free;
  end;
end;

Concurrent Access

// Thread 1
procedure Thread1;
begin
  ThreadSafeList.Add(42);  // Automatically locked
end;

// Thread 2
procedure Thread2;
begin
  if ThreadSafeList.Contains(42) then  // Automatically locked
    WriteLn('Found it!');
end;

Safe Resource Management

var
  Dict: specialize TThreadSafeDictionary<string, integer>;
begin
  Dict := specialize TThreadSafeDictionary<string, integer>.Create;
  try
    // Multiple threads can safely access
    Dict.Add('one', 1);    // Thread 1
    Dict.Add('two', 2);    // Thread 2
    Dict.Remove('one');    // Thread 3
    
    // Safe iteration with RAII locking
    for Pair in Dict do
      WriteLn(Pair.Key, ': ', Pair.Value);
  finally
    Dict.Free;
  end;
end;

✨ Features

  • πŸ›‘οΈ Thread-safe List, Deque, Dictionary and HashSet implementations
  • πŸš€ Generic type support (Integer, String, Real, Boolean, Records)
  • πŸ“¦ Built-in comparers and hash functions
  • πŸ” Automatic locking mechanism with TCriticalSection
  • 🎯 Exception-safe resource management
  • πŸ§ͺ Comprehensive test suite with collision testing
  • ⚑ Optimized performance for common operations
  • πŸ“Š Load factor based automatic resizing

πŸ”„ Feature Comparison

Feature List Deque Dictionary HashSet
Thread-Safe Operations βœ… βœ… βœ… βœ…
RAII Iterator Locking βœ… βœ… βœ… βœ…
Automatic Resizing βœ… βœ… βœ… βœ…
Collision Resolution N/A N/A βœ… βœ…
Specialized Types βœ… ❌ ❌ βœ…
Custom Comparers βœ… ❌ βœ… βœ…
Bulk Operations βœ… βœ… βœ… βœ…
Set Operations N/A N/A N/A βœ…

πŸ§ͺ Testing

  1. Go to tests/ directory
  2. Open TestRunner.lpi in Lazarus IDE and compile
  3. Run ./TestRunner.exe -a -p --format=plain to see the test results.

πŸ“š Documentation

πŸ“ Examples

  • SimpleNumberList - Shows basic operations in TThreadSafeList; Add, Remove, Sort with the built-in integer comparer.
  • SimpleShoppingCart - Shows how to use TThreadSafeList with a custom type and a custom comparer.
  • SimpleToDoList - Shows how to use TThreadSafeList with the built-in string comparer.
  • ChatMessageQueue - Demonstrates using TThreadSafeList for a multi-threaded chat system.
  • DictionaryIterator - Demonstrates using TThreadSafeDictionary with an iterator.
  • DictionaryWithCustomType - Demonstrates using TThreadSafeDictionary with a custom type and a custom comparer.
  • SimpleHashSet - Demonstrates using TThreadSafeHashSet with the built-in integer comparer.
  • SimpleHashSet - Demonstrates using TThreadSafeHashSet with a custom type and a custom comparer.
  • HashSetClientDemo - Demonstrates using TThreadSafeHashSet with a custom type and a custom comparer.
  • SimpleDeque - Demonstrates using TThreadSafeDeque with a custom type and a custom comparer.
  • DequeWithCustomType - Demonstrates using TThreadSafeDeque with a custom type and a custom comparer.

🀝 Contributing

  1. Fork the repository
  2. Create your feature branch
  3. Commit your changes
  4. Push to the branch
  5. Create a Pull Request

πŸ“„ License

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

πŸ‘ Acknowledgments

  • 🎯 Free Pascal and Lazarus community
  • πŸ§ͺ FPCUnit testing framework