This started as a throwaway metaphor in a blog post, but is now fully runnable: a toy RTOS with preemptive multitasking inside of Super Mario Bros. on the NES.
Essentially, this is:
- A rudimentary preemptive RTOS
- Using an unmodified NES emulator (FCEUX) as the CPU
- "Unmodified" depending on how you define terms
- With emulator save states as the thread contexts
- With support for (very basic) mutexes, interrupt masking, and condition variables
- Demonstrated using Super Mario Bros. 1-1 with sections of the map dedicated to various synchronization primitives
There are many simplifications and shortcuts taken (doesn't even have task priorities), and it doesn't map 1:1 to true multithreading (e.g., emulator save states represent the state of the entire machine including RAM, whereas thread contexts represent a much more minimal slice), but I think it's A) pretty interesting and B) a unique visceral explanation of threads.
The metaphor I usually go for when it comes to for threads is having several widgets that need to be assembled. To begin working on Widget B, you have to pause the work on Widget A. Adding SMP is like adding a second person to help assemble widgets, but you're still using the same toolbox, so if you both need the same screwdriver, there's no performance benefit to having a second person. Multicore is having multiple workbenches each with their own toolbox so they can operate completely in parallel.
A mutex is like having a specialized tool that can only be used by one widget assembly at a time. Like, each workbench might have a full set of screwdrivers, hammers, wrenches, sockets, etc., but you only have 1 welder.
A semaphore is a tool that can be used by a limited number of widgets, like an oven that can fit up to 4 widgets.