Threads as trains, locks as single-line tokens: deadlock and data races you can crash.
Each train is a thread looping around one slice of a wagon-wheel railway, and every spoke is single track shared by two neighbors running it in opposite directions. As on a real single-line railway, a driver may only enter a spoke while carrying its token, so each token is a mutex, the semaphore signals show who may pass, and tokens ride on the locomotive's roof. Hand tokens back at the station instead of at the far end and every train ends up holding one spoke while waiting for the next: the dining philosophers, as a gridlock. A signal box in the hub rebuilds the wait-for graph every frame and lights the cycle in red; switch to ordered tokens, an N-1 semaphore or detect-and-back-off to break it, or pull a lock and watch two trains meet head-on.
Try it. Click a signal or token box to switch that spoke's lock off or on. Click a policy to change how drivers take tokens. Keys: 1 to 3 for the presets, P cycles policies, L toggles all locks, + and - add or remove trains, Space pauses, R restarts. Left alone, it tours deadlock, recovery, semaphores and a collision.
Paste this into Claude Code, Codex or any coding agent to get a simple version running, then take it wherever you like.
Build a model railway that teaches concurrency, with JavaScript and the HTML canvas element. Put everything in a single index.html file with no libraries or build step, so I can open it directly in a browser.
Start simple:
- Draw a top-down "wagon wheel": an outer rim, an inner hub ring and five spokes. Each train loops around one slice: along its rim, in along one spoke, around its piece of the hub, and out along the next spoke. Each spoke is shared by the two neighboring trains, running it in opposite directions.
- Sample each loop into points one pixel apart, so a train is just a distance along its path, drawn as a locomotive and two wagons.
- Treat each spoke as a mutex: a train may only enter while holding that spoke's token. Before each spoke put a signal; a train asks for the token as it approaches and brakes smoothly to a stop at a red signal if someone else has it.
- Start with the rule "hand back both tokens at your station, after the whole loop". Every train will hold one spoke while waiting for the next, and the railway deadlocks.
Once that works, make it a lesson:
- Build the wait-for graph each frame (a waiting train points at the train holding the token it needs), find a cycle with a depth-first search, and draw it as red arrows with a big DEADLOCK sign.
- Add policies you can switch between: release each token as soon as you leave the spoke, take tokens in spoke-number order, or a counting semaphore that lets only four trains hold tokens at once.
- Let a click on a signal remove its lock, so two trains can meet head-on with sparks and smoke.
- Show runs completed per train so starvation is visible.
Explain the key ideas in short code comments. When you're done, tell me how to open it and suggest three directions I could take it next, such as a reader-writer junction, priority inversion with a slow freight train, or a detector that picks a victim to back off.