This is my 9th project at 42 Network, a Dining Philosophers project that uses threads to parallelize operations and relies on synchronization mechanisms to avoid data races, such as mutexes.
The objective is to keep the philosophers alive for the maximum amount of time possible.
- Thread Lifecycle & Handlers: Dynamic creation of
pthreadworkers coupled with dedicated execution routines (routines/handlers) per philosopher. - Clean Resource Sanitation: Guaranteeing a 100% leak-free and safe exit by systematically joining threads (
pthread_join) and destroying mutex blocks when termination criteria are met. - Shared Memory Architecture: Exploring how threads, unlike processes, share the exact same address space, highlighting both the speed of memory access and its inherent risks.
- Mutex Orchestration: Hardening critical sections using
pthread_mutexlocks to safely gate access to forks and status variables. - Race Condition & Data Race Elimination: Engineering foolproof state checks to prevent multiple threads from modifying the same variable concurrently.
- Deadlock Prevention: Implementing precise resource acquisition logic (e.g., asymmetric fork-picking protocols) to ensure the system never locks itself into an unresolvable stale state.
- CPU Resource Preservation: Strategic deployment of granular
usleepintervals. This prevents waiting threads from entering tight "busy-waiting" loops, keeping CPU consumption at zero while waiting for a mutex lock. - Task Parallelism: Harnessing true multi-core capabilities by executing independent philosopher lifecycles simultaneously, maximizing execution throughput.
sudo apt install gcc # C compiler
sudo apt install make # Build tool for the Makefile
sudo apt install libc6-dev # C standard library headersgit clone https://github.com/alcarril/Philosofers.git # Download the repository
cd Philosophers/philo # Enter the project directory
make # Compile the program./philo [number_of_philosophers] [time_to_die] [time_to_eat] [time_to_sleep] [max_meals_per_philosopher]./philo 5 800 200 200
β οΈ Hardware & Environment Advisory: This project is specifically tailored and optimized to meet the strict evaluation constraints within the official 42 school campus machines. When dealing with multi-threaded execution loops and microsecond-level timing precision, hardware variance across different CPU architectures, core layouts, and thread scheduling policies is critical. Running this code on external host environments or different processing hardware may directly alter the simulation's behavior and overall timing accuracy.
The simulation implements the classic Dining Philosophers problem under strict resource constraints.
-
The Resource Deficit: One fork per philosopher. A philosopher can only eat after locking both adjacent forks (left
$i$ , right$(i+1) \pmod N$ ), and releases them after eating. - Absolute Isolation: No communication or coordination between neighbors; each thread follows its own loop.
-
The Starvation Clock: If a philosopher fails to start eating within
time_to_diems from the start or last meal, the simulation stops. - Shared Resource Contention: Neighboring forks are shared, so all access must be synchronized to avoid data races and deadlocks.
The scheduler must meet strict timing guarantees under tight time, load, and API constraints:
- Scale & Threshold Limits: Support 200 concurrent threads and remain stable when
time_to_die,time_to_eat,time_to_sleepare as low as 60 ms. - Microsecond-Level Tolerance: Timing lag is punished; in 2-philosopher tests, death notices delayed by >10 ms are invalid.
- Edge-Case Accuracy: Behavior must match the boundary cases below:
- Running
./philo 4 410 200 200must keep all threads alive indefinitely. - Running
./philo 4 310 200 200must cleanly capture exactly one starvation event and terminate. - Running
./philo 5 800 200 200 7must cleanly stop the entire simulation the exact moment every philosopher has eaten at least 7 times.
- Running
π οΈ Authorized Functions: The project can only use the set of authorized functions listed in the Notion page below. π Notion Guide: Funciones Autorizadas
To meet the constraints without fatalities, the project uses a lean multi-threaded architecture plus a dedicated monitor.
- One Thread per Philosopher: Each philosopher runs inside an independent thread loop, executing a continuous state routine (
eatβsleepβthink). - One Mutex per Fork: Every fork is mapped to a unique
pthread_mutex_tto guarantee that no two threads can hold the same resource concurrently. - Asymmetric Fork-Picking Protocol: To completely eliminate deadlocks, odd-numbered philosophers grab their left fork first, while even-numbered philosophers grab their right fork first. This completely breaks the circular wait condition.
The simulation runs a Dedicated Monitor Thread (not a philosopher) that orchestrates global state:
- Starvation Tracking: It constantly reads an
is_deadstate flag modified by the philosophers. If starvation is detected, it instantly flags a globalstop_programvariable. - Meal Threshold Control: It checks if the optional minimum meal count has been reached by all threads to trigger a synchronized halt.
- Clean Termination: Philosophers continuously read the
stop_programflag during their routines, allowing them to stop execution instantly and clear resources cleanly.
Instead of locking every state transition, the design separates reads and writes to reduce contention:
- Philosopher Threads (Leaves): They exclusively write to their own dedicated slot in the
is_dead[n]array upon starvation, and recurrently read the globalstop_programflag. - Monitor Thread (Control Branch): It acts as the central hub. It reads the
is_deadarray and, upon detecting a death or meal completion, safely locks the terminal write-mutex, dumps the exit information, and writestrueto the globalstop_programflag. - The Isolated Case (1 Philo): If only one philosopher is present, the system bypasses fork validation entirely since a single thread can never acquire two forks, routing execution directly to the starvation timer.
An alternative design offloaded all prints to a Logging Thread via a queue to avoid a shared STDOUT mutex in worker threads.
In practice, microsecond-level ordering constraints from the 42 subject exposed queue and context-switch latency, so the design reverted to direct, mutex-locked STDOUT writes for deterministic timestamps.
For a deep, granular breakdown of the operating system mechanics underpinning this project, you can access my personal Notion documentation (written in Spanish). Use the links below to explore each architectural concept:
- Processors and CPU Cores Architecture | π Notion Guide
- Threads Inside a Process: Use Cases and the Thread Control Block (TCB) | π Notion Guide
- Concurrency vs. Parallelism: Structural Differences | π Notion Guide
- Kernel Scheduling: Concurrency, Program Counters (PC), and Core Synchronization | π Notion Guide
- Thread Synchronization and Shared Resource Concurrency Management | π Notion Guide
- Thread Resource Deallocation and Proper Cleanup Lifecycle | π Notion Guide
- Detached vs. Joinable Threads: Task Correlation and Synchronization | π Notion Guide
- Understanding Synchronization Barriers | π Notion Guide
Alejandro Carrillo (alcarril) - GitHub