A thread-safe generic collections library for Free Pascal, designed for learning and experimentation.
Important
π§ Development Status: This library is a learning/experimental project and not yet production-ready.
For production use, please consider these mature alternatives:
- FPC Generics.Collections - Official FPC generic collections
- FCL-STL - FPC's template library
- LGenerics - Comprehensive generics library
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
- β Performance not yet optimized
Planned Features:
- π Performance optimizations
- π More specialized types
- π‘ 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
This library provides four main collection types:
- 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 typesStringComparer
: For string typesBooleanComparer
: For Boolean typesRealComparer
: For Real types
For custom types, implement your own comparer: function MyComparer(const A, B: TMyType): Integer;
- ThreadSafeDeque: A double-ended queue
var
Deque: specialize TThreadSafeDeque<Integer>;
begin
Deque := specialize TThreadSafeDeque<Integer>.Create;
try
Deque.PushBack(1);
Deque.PushFront(2);
WriteLn('Front item: ', Deque.PopFront);
WriteLn('Back item: ', Deque.PopBack);
finally
Deque.Free;
end;
end;
- 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
orCreate(capacity)
- For custom types, use
Create(hashFunc, equalityFunc)
orCreate(capacity, hashFunc, equalityFunc)
- 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;
- Free Pascal 3.2.2 or later
- No external dependencies
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;
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;
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.
{$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;
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;
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;
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;
Recent test results show excellent performance for the HashSet implementation:
Operation | Time (ms) | Items | Notes |
---|---|---|---|
Basic Ops | 0.006 | 10,000 | Add/Contains |
Bulk Add | 0.032 | 100,000 | AddRange |
Set Ops | < 0.001 | 1,000 | Union/Intersect |
Tip
Use bulk operations (AddRange, RemoveRange) for better performance when working with multiple items.
-
Clone the repository:
git clone https://github.com/ikelaiah/ThreadSafeCollections-FP.git
-
Add to Your Project:
-
In Lazarus IDE:
- Project β Project Inspector
- Add Unit β Browse to
src
directory - Select needed units (e.g., ThreadSafeCollections.List.pas)
-
In FPC Project:
{$UNITPATH your/path/to/ThreadSafeCollections-FP/src}
-
- Download ZIP from GitHub
- Extract to your preferred location
- 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
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.
-
Compilation Errors:
- Ensure FPC 3.2.2 or later
- Check unit path is correct
- Verify all required files are present
-
Runtime Errors:
- Check memory management (use try-finally)
- Verify comparers are provided where needed
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;
// 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;
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;
- π‘οΈ 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 | 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 | β |
- Go to
tests/
directory - Open
TestRunner.lpi
in Lazarus IDE and compile - Run
./TestRunner.exe -a -p --format=plain
to see the test results.
- ThreadSafeCollections.List.md
- ThreadSafeCollections.Deque.md
- ThreadSafeCollections.Dictionary.md
- ThreadSafeCollections.HashSet.md
- RAII-style locking through interface counting
- Latest Test Output
- 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.
- Fork the repository
- Create your feature branch
- Commit your changes
- Push to the branch
- Create a Pull Request
This project is licensed under the MIT License - see the LICENSE.md file for details.
- π― Free Pascal and Lazarus community
- π§ͺ FPCUnit testing framework