This project has been created as part of the 42 curriculum by vsoares-
The philosophers project is part of the 42 curriculum and focuses on concurrent programming, synchronization, and thread/process coordination.
The goal is to solve the classic Dining Philosophers Problem: several philosophers alternate between thinking, eating, and sleeping while sharing a limited number of forks. The challenge is to design the simulation so that:
- data races are avoided through proper mutex synchronization,
- deadlocks are prevented via careful fork acquisition ordering,
- starvation is handled according to project rules with continuous monitoring,
- timing and state transitions are correctly monitored and displayed.
This project is mainly about learning how to build safe, deterministic behavior in a multithreaded environment and understanding common pitfalls in concurrent systems.
- A C compiler (
cc/gcc/clang) make- POSIX threads support (
pthread)
From the project root, run:
makeCommon targets (if available in your Makefile):
make→ build the executablemake clean→ remove object filesmake fclean→ remove object files and executablemake re→ full rebuild
Run the program with:
./philo number_of_philosophers time_to_die time_to_eat time_to_sleep [number_of_times_each_philosopher_must_eat]Example:
./philo 5 800 200 200Where:
number_of_philosophers: number of philosophers (and forks)time_to_die: max time (ms) a philosopher can stay without eatingtime_to_eat: time (ms) spent eatingtime_to_sleep: time (ms) spent sleepingnumber_of_times_each_philosopher_must_eat(optional): stop when each philosopher has eaten at least this many times
The program validates all input arguments before starting the simulation:
- Argument Count: Requires exactly 4 or 5 arguments
- Argument Type: All arguments must be valid positive integers
- Zero Check: Number of philosophers, time values, and meal counts cannot be zero
- Value Constraints: Time values (time_to_die, time_to_eat, time_to_sleep) must be greater than zero
If any validation fails, the program exits with an appropriate error message.
The simulation uses a multi-layered mutex system to prevent data races:
-
Global Mutexes (4 total):
death_mutex: Protects the shared death state and meal count limitsnarrator_mutex: Synchronizes output to prevent interleaved print statementsstart_time_mutex: Protects the simulation start timestampsim_started_mutex: Ensures all threads wait for synchronization before beginning
-
Spoon/Fork Mutexes (N total, one per philosopher):
- Each fork is represented as a
t_spoonwith its own mutex - Philosophers must acquire both adjacent forks before eating
- Each fork is represented as a
-
Philosopher Mutexes (N total, one per philosopher):
- Each philosopher's state (last meal time, meal count, death status) is protected
This design ensures that:
- Fork state is exclusively accessed by at most one philosopher at a time
- Death detection and announcements don't race
- Output messages are atomic and correctly ordered
Philosophers acquire forks based on their ID parity to prevent circular wait deadlocks:
-
Even-ID Philosophers (0, 2, 4, ...):
- Pick fork at position
idfirst (first_spoon) - Then pick fork at position
(id + 1) % num_philos(second_spoon)
- Pick fork at position
-
Odd-ID Philosophers (1, 3, 5, ...):
- Pick fork at position
(id + 1) % num_philosfirst (first_spoon) - Then pick fork at position
id(second_spoon)
- Pick fork at position
This ordering breaks the circular dependency that would otherwise cause deadlocks.
When the number of philosophers is odd, the simulation requires special initialization:
-
Philosopher 0's Initial Think Period: Before entering the main eat-sleep-think cycle, philosopher 0 waits for
2 * time_to_eatmilliseconds. This gives other philosophers time to acquire forks and prevents an immediate bottleneck. -
Dynamic Think Time Calculation: For each philosopher during normal cycles, if
2 * time_to_eat > time_to_sleep, the system calculates:time_to_think = (2 * time_to_eat) - time_to_sleepThis ensures philosophers don't wake up too quickly from sleep and starve others waiting for forks.
-
Even-ID Initial Think: Even-ID philosophers (except 0) also think for
time_to_eatmilliseconds before their first eating attempt, ensuring staggered fork acquisition.
The monitoring system continuously checks for starvation:
- Per-Philosopher Monitoring: Each philosopher tracks its
last_meal_time - Death Check: If
(current_time - last_meal_time) >= time_to_die, the philosopher is marked dead - Single Death Announcement: The first philosopher to die has its death printed exactly once to the output
- Simulation End: The monitoring loop exits when either:
- A philosopher dies, OR
- All philosophers have eaten
number_of_times_each_philosopher_must_eattimes (if specified)
The narration system ensures atomic, correctly-ordered output:
- Thread-Safe Printing: All output is protected by
narrator_mutexto prevent interleaved messages - Death Announcement: Death messages are announced exactly once using a static flag, even if multiple threads detect starvation
- Simulation End Detection: Non-death messages are suppressed after the simulation ends (either due to death or meal completion)
- Timestamps: Each message includes milliseconds elapsed since simulation start
- Timestamps are shown in milliseconds from simulation start.
- The simulation ends when a philosopher dies, or when the optional meal count condition is met and all philosophers are full.
- Use small/large timing values carefully when testing edge cases.
Concept & Theory:
- Dining Philosophers Problem (Wikipedia)
https://en.wikipedia.org/wiki/Dining_philosophers_problem - Philosophers 42 Guide: The Dining Philosophers Problem
https://medium.com/@ruinadd/philosophers-42-guide-the-dining-philosophers-problem-893a24bc0fe2 - Code Vault: Philosophers Course
https://code-vault.net/course/6q6s9eerd0:1609007479575
Man Pages (Linux man-pages):
- POSIX Threads Overview
https://man7.org/linux/man-pages/man7/pthreads.7.html - Mutex: pthread_mutex_lock and related functions
https://man7.org/linux/man-pages/man3/pthread_mutex_lock.3p.html
AI was used as a support tool for:
- reviewing README structure and clarity,
- improving wording and documentation quality,
- checking consistency of command descriptions and parameter explanations.
AI was not used to replace project understanding or core implementation decisions. Final validation, code logic, and project behavior remain the author's responsibility.
The repository usually contains:
- source files for philosopher logic, timing, and synchronization,
- a
Makefilefor building the executable, - this
README.mdfor setup and project context, en.philosophers.pdf- English version of the 42 philosophers project specification.
- Minimal valid case (e.g., 1 philosopher)
- Typical case (e.g., 2–5 philosophers)
- Stress case with many philosophers (up to 200)
- Cases near timing limits to validate death detection and race safety
- Odd vs. even philosopher counts to verify special initialization logic