A high-performance, thread-safe hash map implementation in C++ using lock-free algorithms and atomic operations. Achieves up to 22.84x speedup over mutex-based implementations on read-heavy workloads.
- Lock-Free Operations: Insert, get, and remove using Compare-And-Swap (CAS)
- Thread-Safe: Safe concurrent access from multiple threads
- High Performance: Scales efficiently with thread count
- Logical Deletion: Safe memory management using deletion markers
- Verified Memory Safety: Tested with AddressSanitizer
Benchmark results comparing against std::unordered_map with mutex protection (8 threads, 50k ops/thread):
| Workload | Lock-Free | Mutex-Based | Speedup |
|---|---|---|---|
| Read-Only | 0.70 ms | 16.08 ms | 22.84x ⚡ |
| Insert-Only | 3.08 ms | 68.28 ms | 22.18x ⚡ |
| Read-Heavy (80/20) | 15.46 ms | 37.71 ms | 2.44x |
| Mixed (50/50) | 32.67 ms | 70.46 ms | 2.16x |
- Exceptional Scalability: Nearly linear scaling with thread count for reads and inserts
- No Lock Contention: Lock-free design eliminates blocking and context switches
- Sweet Spot: 2+ threads with any workload mix
- Consistent Performance: Even single-threaded performance is competitive
Use lock-free hash map when:
- High thread contention (2+ concurrent threads)
- Any read/write mix
- Predictable latency is critical
- Cannot tolerate lock contention
Use mutex-based map when:
- Strictly single-threaded
- Memory footprint is absolutely critical (logical deletion keeps removed nodes)
- Simpler debugging is preferred
LockFreeHashMap
├── Atomic bucket array (std::atomic<Node*>[])
├── Lock-free linked lists for collision chains
├── Logical deletion with atomic flags
└── CAS-based operations for thread safety
Insert Operation:
- Hash key to bucket index
- Create new node
- Attempt CAS to prepend to chain
- Retry on conflict (lock-free retry loop)
- Note: Allows duplicate keys (most recent value returned by get)
Get Operation:
- Hash key to bucket index
- Traverse chain atomically
- Skip logically deleted nodes
- Return first matching non-deleted key
Remove Operation:
- Hash key to bucket index
- Find node with matching key
- Mark as logically deleted using CAS
- Physical deletion deferred until map destruction
Uses logical deletion for safety and performance:
- Removed nodes marked with atomic flag
- Memory reclaimed only at map destruction
- Eliminates use-after-free bugs
- Simpler than hazard pointers with better performance
Tradeoff: Memory accumulates for removed keys until the map is destroyed. For applications with many deletions relative to the map's lifetime, consider periodic reconstruction.
#include "lockfree_hashmap.hpp"
LockFreeHashMap<std::string, int> map(1024);
// Insert (allows duplicates, most recent wins)
map.insert("key", 42);
map.insert("key", 43); // Updates value
// Get (returns most recent non-deleted value)
int value;
if (map.get("key", value)) {
std::cout << "Found: " << value << "\n"; // Prints: Found: 43
}
// Remove (logical deletion)
map.remove("key");
// Memory freed when map is destroyedmkdir build && cd build
cmake ..
make./demo # Basic functionality
./stress_test # 80k concurrent operations
./benchmark # Performance comparison
./memory_test # 100k removal test
./sanitizer_test # Memory safety verificationmkdir build-sanitizer && cd build-sanitizer
cmake -DCMAKE_CXX_FLAGS="-fsanitize=address -g" ..
make sanitizer_test
./sanitizer_test- C++17 or later
- CMake 3.20+
- Compiler with atomic support (GCC, Clang, MSVC)
- pthreads
- Correctness: Multi-threaded stress tests with 80k concurrent operations
- Performance: Benchmarked against mutex-protected std::unordered_map
- Memory Safety: Verified with AddressSanitizer (no leaks, no use-after-free)
- Thread Safety: All operations are lock-free and linearizable
- Acquire-Release semantics for CAS operations
- Relaxed ordering for internal node linking
- Ensures visibility guarantees across threads
The map allows duplicate keys (most recent insert wins). This is a standard design choice for lock-free hash maps because:
- Checking for duplicates in a lock-free way is expensive and error-prone
- Most applications either don't insert duplicates or don't care about the order
get()always returns the most recently inserted value
- Fixed bucket count at construction (no dynamic resizing)
- Logical deletion accumulates memory until destruction
- No iterator support
- Dynamic resizing with concurrent rehashing
- Epoch-based reclamation for physical deletion
- Iterator support with snapshot isolation
- Custom allocator support
Traditional mutex-based concurrent data structures suffer from:
- Lock contention at high thread counts
- Context switching overhead
- Priority inversion
- Potential deadlocks
Lock-free algorithms eliminate these issues through atomic operations, providing:
- Guaranteed system-wide progress
- Better scalability
- Lower latency
- No blocking
We chose logical deletion over physical deletion (hazard pointers/epoch-based reclamation) because:
- Simplicity: No complex memory reclamation protocol
- Performance: No overhead from tracking protected pointers
- Safety: Eliminates use-after-free bugs entirely
- Appropriate: For most use cases, memory footprint is acceptable
Physical deletion would be needed for:
- Long-running servers with high delete rates
- Memory-constrained environments
- Maps with unbounded growth
MIT License - See LICENSE file for details
Built to demonstrate advanced concurrent programming techniques and lock-free algorithm implementation in systems programming.
Note: This is an educational/portfolio project demonstrating lock-free concurrent data structure design. For production use, consider battle-tested libraries like Intel TBB, Folly, or libcds.
Design inspired by:
- Michael & Scott lock-free queue algorithms
- Intel TBB concurrent_hash_map
- "The Art of Multiprocessor Programming" by Herlihy & Shavit