concurrency-dining-philosophers
The Dining Philosophers problem is solved using resource hierarchy (also called the "partial ordering" solution) with Go's sync.Mutex for fork locking, a chan struct{} for coordination, and ANSI terminal animation.
Key design:
| Component | Purpose |
|---|---|
sync.Mutex on each Fork |
Exclusive access to shared forks |
chan struct{} |
Signal when all philosophers finish dining |
sync.WaitGroup |
Wait for all goroutines to complete |
| Resource hierarchy | Prevents circular wait deadlock |
| ANSI escape sequences | Animate terminal output |
Resource hierarchy rule: Each philosopher picks up the lower-numbered fork first, then the higher-numbered one. Since fork numbers are totally ordered, this breaks the circular wait condition — it's impossible for all 5 philosophers to hold one fork and wait for another in a cycle.
// --- Resource hierarchy: pick up lower-numbered fork first ---
first, second := p.leftFork, p.rightFork
if p.leftFork.id > p.rightFork.id {
first, second = p.rightFork, p.leftFork
}
first.mu.Lock()
second.mu.Lock()
// ... eat ...
second.mu.Unlock()
first.mu.Unlock()
For example, P4 has left=Fork4, right=Fork0. Since Fork0 < Fork4, P4 always picks Fork0 first, then Fork4. Meanwhile P0 has left=Fork0, right=Fork1 and since Fork0 < Fork1, P0 picks Fork0 first too — so P0 and P4 can never form a cycle holding Fork4↔Fork0.
The program was run with Go 1.26 and produced the output shown above. All 5 philosophers ate exactly 3 times each, with all states transitioning correctly through 🤔 THINKING → 😋 HUNGRY → 🍝 EATING → ✅ DONE. **Edge cases tested:** - **All philosophers hungry simultaneously** — the hierarchy ensures at least one philosopher can always acquire both forks (P4's first fork is Fork0, which is also P0's first fork, but P0's second fork is Fork1 — no cycle) - **Concurrent fork contention** — `sync.Mutex` handles competition; blocked goroutines wait correctly - **Final state** — all philosophers reach DONE, confirmed via channel counting (`doneCh`) - **Animated rendering** — ticker-driven redraw at 80ms intervals with shared state protected by `muStates` ---
{"model": "<model>", "problem_class": "concurrency-dining-philosophers", "result": "passed", "tests": 5}