Overview
An implementation of the classic Dining Philosophers concurrency problem in C using POSIX threads, where each philosopher runs as its own thread alternating between thinking, eating, and sleeping while sharing a fixed number of forks with its neighbors. The simulation enforces strict timing rules, a philosopher who doesn't eat before a configurable time-to-die limit is detected as dead, so correctness depends as much on precise timing as on avoiding deadlocks and race conditions.
Architecture
Each philosopher runs on its own pthread executing a shared routine that cycles through thinking, taking two fork mutexes, eating, releasing them, and sleeping. A separate monitor thread runs concurrently, checking every philosopher's last-meal timestamp against the time-to-die limit to detect starvation without interrupting the eating threads. A dedicated timing helper breaks sleep calls into short intervals so the monitor can still detect a death promptly instead of oversleeping past the deadline.
Key Features
- Independent philosopher threads synchronized with per-fork mutexes to prevent two neighbors eating with the same fork at once
- Dedicated monitor thread that detects philosopher death based on a configurable time-to-die limit
- Optional meal-count limit so the simulation can end once every philosopher has eaten enough times
- Millisecond-precision timing via a custom sleep helper to avoid missing the death deadline
Technical Challenges
- Preventing deadlock when every philosopher tries to pick up forks at once, without serializing them so heavily that the simulation stops behaving concurrently
- Detecting a philosopher's death within the required timing precision without a busy-wait loop burning CPU or a long sleep call missing the deadline
- Protecting shared state, last meal time and the death flag, from race conditions between each philosopher thread and the monitor thread
Engineering Decisions
- Used a dedicated monitor thread instead of having each philosopher check its own death condition, since a philosopher blocked waiting for a fork can't reliably watch its own clock
- Broke long sleeps into short intervals rather than a single sleep call, trading a small amount of CPU overhead for the timing precision the monitor depends on
- Guarded shared timing state with mutexes rather than atomics, keeping the synchronization model consistent with the rest of the fork-locking design
Technologies Used
C with POSIX threads (pthread) and mutexes for concurrency, built and tested on Linux.
Lessons Learned
- Built a practical understanding of thread synchronization and the classic deadlock/starvation trade-offs beyond the textbook description
- Learned how much correctness in a real-time-constrained concurrent system depends on timing precision, not just locking correctness
- Practiced isolating shared mutable state and reasoning carefully about which thread is allowed to touch it and when