elouann.dev
FR — version française

ProjectsLow-level & embedded

Philosophers

Threads, mutexes and data races

Period
Mar 2025
Context
42 curriculum
Team
solo
Grade
100/100 · outstanding ×1
  • C
  • Low-level
  • Algorithms
  • Imperative programming

View source

The dining philosophers: philosophers around a table, one fork between each of them, and eating takes two. Each philosopher is a thread, each fork a mutex, and nobody must starve.

the tableP1P2P3P4P51 philosopher = 1 thread1 fork = 1 mutexP1 eats: both of its forks are takena thread’s cyclethinksforksboth, or noneeatst_eatsleepst_sleepevery iteration:dead, or a neighbour dead?shared mutexesoutputdeathmeals
DiagramThe table (here with five philosophers) and the cycle each thread follows. Shared mutexes protect output and the announcement of a death.

The rules

The program takes the number of philosophers, the time to die, the time to eat, the time to sleep, and optionally a number of meals. A philosopher who has not eaten for too long dies, and the simulation must stop at once, reporting it to the millisecond.

My solution

Each fork is a “mutex + value” pair. A thinking philosopher locks both forks, checks that they are free, marks them as taken, then releases the locks: they never hold a mutex while eating, which avoids deadlocks.

Each thread follows a small state machine (think, take forks, eat, sleep) and checks on every iteration whether it has run out of time or a neighbour has died. The meal counter, the “alive” flag and the output each have their own mutex: no shared data is read without a lock.

A bit of fun

The philosophers have names, numbered in Roman numerals, and the output exists in French and English, chosen at compile time.

What I took away

  • A data race is not always visible: you prevent it, one lock at a time.
  • Hold a mutex for as short a time as possible.
  • A thread’s “real” timing depends on the scheduler, not only on the code.