A high-performance banking simulation engine implemented in C, designed to handle massive parallel transaction loads. This project demonstrates advanced systems programming concepts, focusing on thread safety, deadlock prevention, and race condition elimination in a shared-memory environment.
- Fine-Grained Concurrency: Implements individual mutex locks for every Account and Branch (rather than a single global lock), maximizing throughput by allowing non-conflicting transactions to proceed in parallel.
- Deadlock Prevention: Enforces a strict hierarchical locking protocol (Lock Order: Branch ID -> Account ID) to mathematically eliminate circular wait conditions.
- Atomic Transactions: Guarantees ACID-like properties for cross-branch transfers. Money is never "lost" or "duplicated" in flight, even if context switches occur mid-transaction.
- Custom Synchronization Primitives: Features a custom barrier implementation using Condition Variables (pthread_cond) to synchronize worker threads for end-of-day reporting.
In naive implementations, checking a balance and updating it are separate instructions. In a multithreaded environment, a context switch between these steps leads to "dirty reads" and inconsistent global states. Solution: Designed a "Branch-First" locking strategy. The worker thread acquires all necessary locks (Source Branch, Dest Branch, Source Account, Dest Account) before executing any logic, ensuring the transaction is atomic and isolated.
During development, implicit function declarations caused 64-bit account IDs to be truncated to 32 bits during bit-shifting operations, leading to memory addressing errors. Solution: Enforced strict function prototyping and utilized manual bit-shifting with uint64_t casting to guarantee correct memory addressing across different system architectures.
The system was rigorously stress-tested using Valgrind's Helgrind tool. Result: 0 Data Races, 0 Deadlocks, and 0 Lock Order Violations under heavy load (16+ concurrent threads).
To compile the multi-threaded engine:
make
The simulation runs via the command-line interface, allowing users to configure worker thread counts and stress-test parameters.
./bank_engine -t[TEST_ID] -w[WORKER_COUNT] [OPTIONS]
- -wN: Sets the number of concurrent worker threads (Tellers).
- Example: -w16 runs the simulation with 16 parallel threads.
- -tN: Selects the workload scenario (varying branch/account topology).
- -r: Race-checker reduction mode (optimizes execution for Helgrind analysis).
- -b: Integrity Check. Enables periodic global balance verification to prove conservation of money.
- -yN: Yield Injection. Forces threads to voluntarily yield the CPU N% of the time, artificially increasing context switching to stress-test lock boundaries.
├── src/
│ ├── bank.c # Core banking logic and transaction handling
│ ├── teller.c # Worker thread implementation
│ └── report.c # Thread-safe reporting and barriers
├── include/
│ └── ledger.h # Data structures and lock definitions
├── Makefile
└── README.md
Author: Luka Aladashvili