Below is the round table. Each philosopher tries to pick up her left fork, then her right fork. Press Run (naive) and watch what happens when they all reach for the left fork together.
<p class="hint">Five philosophers, five forks. To eat, a philosopher needs both neighboring forks. Run the naive protocol (everyone grabs left first) and watch them deadlock — then pick a fix and see everyone eat.</p>
<div class="modes">
<label><input type="radio" name="mode" value="naive" checked> Naive (left then right)</label>
<label><input type="radio" name="mode" value="order"> Resource ordering</label>
<label><input type="radio" name="mode" value="waiter"> Waiter (max 4 seated)</label>
</div>
<div class="stage"><svg id="table" viewBox="0 0 320 320" aria-label="round table"></svg></div>
<div class="status" id="status">Pick a mode, then press Run.</div>
<div class="counters">
<span>Meals served: <b id="meals">0</b></span>
<span>Ticks: <b id="ticks">0</b></span>
</div>
<div class="btns">
<button id="run" type="button">Run</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.modes { display: flex; flex-direction: column; gap: .25rem; font-size: .88rem; margin: 0 0 .6rem; }
.modes label { cursor: pointer; }
.stage { display: flex; justify-content: center; }
svg { width: 300px; height: 300px; max-width: 100%; }
.phil { fill: #e8eef3; stroke: #cdd9e3; stroke-width: 2; }
.phil.hungry { fill: #f2d27a; stroke: #d9ad3a; }
.phil.eating { fill: #6fcf7f; stroke: #3aa64f; }
.phil.blocked { fill: #e88c8c; stroke: #c64545; }
.plabel { font: 700 13px ui-monospace, monospace; fill: #1d3557; text-anchor: middle; }
.fork { stroke: #adb1b8; stroke-width: 5; stroke-linecap: round; }
.fork.held { stroke: #1d3557; }
.status { font-size: 1rem; font-weight: 600; margin: .55rem 0 .2rem; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.counters { display: flex; gap: 1.2rem; font-size: .9rem; color: #444; margin: .1rem 0 .6rem; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Dining philosophers (Dijkstra, 1965). N philosophers, N forks on a ring.
// Fork i sits between philosopher i and philosopher (i+1)%N.
// Philosopher i's LEFT fork = i, RIGHT fork = (i-1+N)%N (depends on seating; here we just need a fixed, distinct pair per philosopher).
const N = 5;
const leftFork = i => i; // fork on the left of philosopher i
const rightFork = i => (i + 1) % N; // fork on the right of philosopher i
// forkOwner[f] = philosopher index holding fork f, or -1 if free.
let forkOwner, state, meals, ticks, timer, mode;
function reset() {
forkOwner = Array(N).fill(-1);
state = Array(N).fill('thinking'); // thinking | hungry | eating
meals = 0; ticks = 0;
if (timer) { clearInterval(timer); timer = null; }
setStatus('Pick a mode, then press Run.', '');
draw();
}
// Which two forks philosopher i reaches for, in pickup order. Under "resource
// ordering" she always takes the LOWER-numbered fork first, which breaks the cycle.
function order2(i) {
let a = leftFork(i), b = rightFork(i);
if (mode === 'order' && a > b) return [b, a];
return [a, b];
}
// One tick models a simultaneous round in two phases, so the naive policy can
// reach the classic deadlock: everyone grabs a first fork before anyone grabs a second.
function tick() {
ticks++;
let progressed = false;
const seated = () => state.filter(s => s !== 'thinking').length;
// Phase A: finish meals, wake thinkers, grab FIRST fork (all at once).
for (let i = 0; i < N; i++) {
if (state[i] === 'eating') {
forkOwner[leftFork(i)] = -1; forkOwner[rightFork(i)] = -1;
state[i] = 'thinking'; meals++; progressed = true; continue;
}
if (state[i] === 'thinking') {
if (mode === 'waiter' && seated() >= N - 1) continue; // waiter blocks a 5th diner
state[i] = 'hungry'; progressed = true;
}
if (state[i] === 'hungry') {
const [a] = order2(i);
if (forkOwner[a] === -1) { forkOwner[a] = i; progressed = true; }
}
}
// Phase B: grab SECOND fork if free; if both are held, eat.
for (let i = 0; i < N; i++) {
if (state[i] !== 'hungry') continue;
const [a, b] = order2(i);
if (forkOwner[a] === i && forkOwner[b] === -1) { forkOwner[b] = i; progressed = true; }
if (forkOwner[a] === i && forkOwner[b] === i) { state[i] = 'eating'; progressed = true; }
}
const blocked = state.every(s => s === 'hungry') &&
forkOwner.every(o => o !== -1);
if (blocked) {
for (let i = 0; i < N; i++) state[i] = 'hungry';
draw(true);
setStatus('☠ DEADLOCK: every philosopher holds one fork and waits forever.', 'bad');
clearInterval(timer); timer = null; return;
}
draw();
if (meals >= 8) {
setStatus(fmt('✓ Everyone keeps eating — no deadlock, %meals% meals served.', { meals: meals }), 'ok');
clearInterval(timer); timer = null; return;
}
if (!progressed) {
setStatus('Stuck: no philosopher can make progress.', 'bad');
clearInterval(timer); timer = null;
}
}
function run() {
reset();
mode = document.querySelector('input[name=mode]:checked').value;
setStatus(fmt('Running "%mode%"…', { mode: mode }), '');
timer = setInterval(tick, 420);
}
// ---- drawing ----
const cx = 160, cy = 160, R = 95, fr = 70;
function pos(angle, r) { return [cx + r * Math.cos(angle), cy + r * Math.sin(angle)]; }
function draw(deadlock) {
const svg = document.getElementById('table');
let s = '<circle cx="160" cy="160" r="118" fill="none" stroke="#e3e7ec" stroke-width="2"/>';
// forks sit between philosophers, at angle offset by half a slot
for (let f = 0; f < N; f++) {
const ang = (f + 0.5) * (2 * Math.PI / N) - Math.PI / 2;
const [x, y] = pos(ang, fr);
const held = forkOwner[f] !== -1;
s += '<line class="fork' + (held ? ' held' : '') + '" x1="' + (x - 9) +
'" y1="' + (y - 9) + '" x2="' + (x + 9) + '" y2="' + (y + 9) + '"/>';
s += '<text x="' + x + '" y="' + (y + 18) + '" font-size="9" fill="#888" text-anchor="middle">f' + f + '</text>';
}
for (let i = 0; i < N; i++) {
const ang = i * (2 * Math.PI / N) - Math.PI / 2;
const [x, y] = pos(ang, R);
let cls = 'phil ' + (state[i] === 'eating' ? 'eating' :
state[i] === 'hungry' ? (deadlock ? 'blocked' : 'hungry') : '');
s += '<circle class="' + cls.trim() + '" cx="' + x + '" cy="' + y + '" r="22"/>';
s += '<text class="plabel" x="' + x + '" y="' + (y + 5) + '">P' + i + '</text>';
}
svg.innerHTML = s;
}
function setStatus(t, cls) { const e = document.getElementById('status'); e.textContent = t; e.className = 'status ' + cls;
document.getElementById('meals').textContent = meals; document.getElementById('ticks').textContent = ticks; }
document.getElementById('run').addEventListener('click', run);
document.getElementById('reset').addEventListener('click', reset);
document.querySelectorAll('input[name=mode]').forEach(r => r.addEventListener('change', reset));
reset();
<p class="hint">Five philosophers, five forks. To eat, a philosopher needs both neighboring forks. Run the naive protocol (everyone grabs left first) and watch them deadlock — then pick a fix and see everyone eat.</p>
<div class="modes">
<label><input type="radio" name="mode" value="naive" checked> Naive (left then right)</label>
<label><input type="radio" name="mode" value="order"> Resource ordering</label>
<label><input type="radio" name="mode" value="waiter"> Waiter (max 4 seated)</label>
</div>
<div class="stage"><svg id="table" viewBox="0 0 320 320" aria-label="round table"></svg></div>
<div class="status" id="status">Pick a mode, then press Run.</div>
<div class="counters">
<span>Meals served: <b id="meals">0</b></span>
<span>Ticks: <b id="ticks">0</b></span>
</div>
<div class="btns">
<button id="run" type="button">Run</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.modes { display: flex; flex-direction: column; gap: .25rem; font-size: .88rem; margin: 0 0 .6rem; }
.modes label { cursor: pointer; }
.stage { display: flex; justify-content: center; }
svg { width: 300px; height: 300px; max-width: 100%; }
.phil { fill: #e8eef3; stroke: #cdd9e3; stroke-width: 2; }
.phil.hungry { fill: #f2d27a; stroke: #d9ad3a; }
.phil.eating { fill: #6fcf7f; stroke: #3aa64f; }
.phil.blocked { fill: #e88c8c; stroke: #c64545; }
.plabel { font: 700 13px ui-monospace, monospace; fill: #1d3557; text-anchor: middle; }
.fork { stroke: #adb1b8; stroke-width: 5; stroke-linecap: round; }
.fork.held { stroke: #1d3557; }
.status { font-size: 1rem; font-weight: 600; margin: .55rem 0 .2rem; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.counters { display: flex; gap: 1.2rem; font-size: .9rem; color: #444; margin: .1rem 0 .6rem; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Dining philosophers (Dijkstra, 1965). N philosophers, N forks on a ring.
// Fork i sits between philosopher i and philosopher (i+1)%N.
// Philosopher i's LEFT fork = i, RIGHT fork = (i-1+N)%N (depends on seating; here we just need a fixed, distinct pair per philosopher).
const N = 5;
const leftFork = i => i; // fork on the left of philosopher i
const rightFork = i => (i + 1) % N; // fork on the right of philosopher i
// forkOwner[f] = philosopher index holding fork f, or -1 if free.
let forkOwner, state, meals, ticks, timer, mode;
function reset() {
forkOwner = Array(N).fill(-1);
state = Array(N).fill('thinking'); // thinking | hungry | eating
meals = 0; ticks = 0;
if (timer) { clearInterval(timer); timer = null; }
setStatus('Pick a mode, then press Run.', '');
draw();
}
// Which two forks philosopher i reaches for, in pickup order. Under "resource
// ordering" she always takes the LOWER-numbered fork first, which breaks the cycle.
function order2(i) {
let a = leftFork(i), b = rightFork(i);
if (mode === 'order' && a > b) return [b, a];
return [a, b];
}
// One tick models a simultaneous round in two phases, so the naive policy can
// reach the classic deadlock: everyone grabs a first fork before anyone grabs a second.
function tick() {
ticks++;
let progressed = false;
const seated = () => state.filter(s => s !== 'thinking').length;
// Phase A: finish meals, wake thinkers, grab FIRST fork (all at once).
for (let i = 0; i < N; i++) {
if (state[i] === 'eating') {
forkOwner[leftFork(i)] = -1; forkOwner[rightFork(i)] = -1;
state[i] = 'thinking'; meals++; progressed = true; continue;
}
if (state[i] === 'thinking') {
if (mode === 'waiter' && seated() >= N - 1) continue; // waiter blocks a 5th diner
state[i] = 'hungry'; progressed = true;
}
if (state[i] === 'hungry') {
const [a] = order2(i);
if (forkOwner[a] === -1) { forkOwner[a] = i; progressed = true; }
}
}
// Phase B: grab SECOND fork if free; if both are held, eat.
for (let i = 0; i < N; i++) {
if (state[i] !== 'hungry') continue;
const [a, b] = order2(i);
if (forkOwner[a] === i && forkOwner[b] === -1) { forkOwner[b] = i; progressed = true; }
if (forkOwner[a] === i && forkOwner[b] === i) { state[i] = 'eating'; progressed = true; }
}
const blocked = state.every(s => s === 'hungry') &&
forkOwner.every(o => o !== -1);
if (blocked) {
for (let i = 0; i < N; i++) state[i] = 'hungry';
draw(true);
setStatus('☠ DEADLOCK: every philosopher holds one fork and waits forever.', 'bad');
clearInterval(timer); timer = null; return;
}
draw();
if (meals >= 8) {
setStatus(fmt('✓ Everyone keeps eating — no deadlock, %meals% meals served.', { meals: meals }), 'ok');
clearInterval(timer); timer = null; return;
}
if (!progressed) {
setStatus('Stuck: no philosopher can make progress.', 'bad');
clearInterval(timer); timer = null;
}
}
function run() {
reset();
mode = document.querySelector('input[name=mode]:checked').value;
setStatus(fmt('Running "%mode%"…', { mode: mode }), '');
timer = setInterval(tick, 420);
}
// ---- drawing ----
const cx = 160, cy = 160, R = 95, fr = 70;
function pos(angle, r) { return [cx + r * Math.cos(angle), cy + r * Math.sin(angle)]; }
function draw(deadlock) {
const svg = document.getElementById('table');
let s = '<circle cx="160" cy="160" r="118" fill="none" stroke="#e3e7ec" stroke-width="2"/>';
// forks sit between philosophers, at angle offset by half a slot
for (let f = 0; f < N; f++) {
const ang = (f + 0.5) * (2 * Math.PI / N) - Math.PI / 2;
const [x, y] = pos(ang, fr);
const held = forkOwner[f] !== -1;
s += '<line class="fork' + (held ? ' held' : '') + '" x1="' + (x - 9) +
'" y1="' + (y - 9) + '" x2="' + (x + 9) + '" y2="' + (y + 9) + '"/>';
s += '<text x="' + x + '" y="' + (y + 18) + '" font-size="9" fill="#888" text-anchor="middle">f' + f + '</text>';
}
for (let i = 0; i < N; i++) {
const ang = i * (2 * Math.PI / N) - Math.PI / 2;
const [x, y] = pos(ang, R);
let cls = 'phil ' + (state[i] === 'eating' ? 'eating' :
state[i] === 'hungry' ? (deadlock ? 'blocked' : 'hungry') : '');
s += '<circle class="' + cls.trim() + '" cx="' + x + '" cy="' + y + '" r="22"/>';
s += '<text class="plabel" x="' + x + '" y="' + (y + 5) + '">P' + i + '</text>';
}
svg.innerHTML = s;
}
function setStatus(t, cls) { const e = document.getElementById('status'); e.textContent = t; e.className = 'status ' + cls;
document.getElementById('meals').textContent = meals; document.getElementById('ticks').textContent = ticks; }
document.getElementById('run').addEventListener('click', run);
document.getElementById('reset').addEventListener('click', reset);
document.querySelectorAll('input[name=mode]').forEach(r => r.addEventListener('change', reset));
reset();
The naive run slides into deadlock: five forks held, five philosophers blocked, zero meals served. Now switch on a fix. Resource ordering numbers the forks and forces everyone to pick up the lower-numbered fork first — one philosopher ends up reaching the "wrong" way, breaks the circular wait, and dinner proceeds. A waiter instead admits at most four philosophers to the table at once, so there is always a free fork somewhere. Either way the freeze disappears.
Comments
Loading comments...