Skip to content

aphonogelia/philosophers

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

philosophers

C implementation of the Dining Philosophers problem using POSIX threads and mutexes.


Overview

Simulates philosophers alternating between eating, sleeping, and thinking. A dedicated witness thread monitors the simulation and stops it when a philosopher dies (time since last meal exceeds time_to_die), or when all philosophers have eaten the required number of meals.


Build & run

make
./philo number_of_philosophers time_to_die time_to_eat time_to_sleep [number_of_times_each_philosopher_must_eat]

All time values are in milliseconds. The meal count argument is optional.

# 5 philosophers, no meal limit
./philo 5 800 200 200

# 5 philosophers, stop after 7 meals each
./philo 5 800 200 200 7

Make targets: make · make clean · make fclean · make re · make debug (thread sanitizer) · make leaks (address sanitizer)


Output

<timestamp_ms> <philosopher_id> <status>

Possible statuses: has taken a fork · is eating · is sleeping · is thinking · died

Timestamps are relative to simulation start. All printing and shared state access is mutex-protected.


Concurrency model

Each philosopher runs in its own POSIX thread, competing for two shared resources (forks) represented as mutexes. The core challenge is avoiding three classes of failure simultaneously: deadlock, starvation, and data races.

Deadlock prevention — the naive approach (each philosopher picks up the left fork, then waits for the right) deadlocks immediately when all philosophers pick up their left fork at once. The fix here is fork ordering: even-numbered philosophers pick up the right fork first, odd-numbered pick up the left. This breaks the circular wait condition.

Data races — every read or write to shared state (last meal timestamp, meal count, death flag) is protected by a dedicated mutex. This includes printing: without a print mutex, log lines from concurrent threads interleave and produce garbled output. A common mistake is reading time_since_last_meal without holding the lock — this is a race even if the write is atomic on the target architecture, because C gives no such guarantee.

Timing precisionusleep is not reliable for short durations; the OS may oversleep by several milliseconds, which causes philosophers to die on tight timing configurations (e.g. ./philo 5 310 200 200). The solution is a busy-wait loop that polls gettimeofday until the target time is reached, sleeping in short bursts to avoid burning CPU. Timestamps are taken with gettimeofday rather than clock() to measure wall time, not CPU time.

Death detection — the witness thread polls each philosopher's last meal timestamp at a high frequency. The key constraint is that the death message must be printed within 10ms of actual death. This means the witness must run tightly and the death flag must be checked before any philosopher attempts to acquire a fork — otherwise a dead philosopher could emit a spurious has taken a fork line.

The single philosopher edge case — with one philosopher and one fork, no solution based on two-fork acquisition works. A dedicated routine handles this: the philosopher picks up the single fork, waits, and dies. No deadlock logic applies.


Notes

  • The witness thread polls state independently to detect death or completion without blocking philosophers.
  • make debug enables -fsanitize=thread to catch data races at runtime — useful for verifying mutex coverage.

About

POSIX shell in C — lexer, parser, pipes, redirections, and builtins from scratch

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors