3 ms·
A simple way to think about it is: 1. The state of the application is at rest. 2. An event happens (a message is received, a timeout, a key press, …) and the
by deterministic 3y ago
A simple way to think about it is:
1. The state of the application is at rest.
2. An event happens (a message is received, a timeout, a key press, …) and the state of the application is updated to a new state.
3. Goto 1
In other words, think about the application as a large state machine with each event being a transition from one state to another. It’s that simple really.
How the events are detected and processed is system/OS/API/… dependent. It can be done in many ways. But the basic idea is the same.
- hinkley 3y agoOnly if the system does not have any other concurrency primitives, and if events are not recursive. Either will break your event system in ways people have trouble understanding. At one point in my career I’d seen and heard this failure mode so often I swore never to use one again. I don’t mean never use events, that would not be “crazy like a fox” but just regular crazy. I mean not as the organizing principle. When event dispatch is small, isolated islands of state, then the isolation prevents this from happening. When it’s central and everyone is convinced to use it, then errors accumulate. No one can watch all parts of the system for nasty bugs. We pick our battles. But when all parts of the system (can) touch the same thing, then you have to assume everything touches everything else. If you take the right snippets out of what I just wrote, you might think I was talking about global shared state. Which it pretty much is.
- deterministic 3y agoRecursive events is a big red flag. Every time I have seen that in the wild it was a really bad and unnecessary design choice. Using parallelism/concurrency is fine. As long as the data dependencies doesn’t have the usual multi-threading bugs (a thread modifying data that another thread is reading etc.)
- diarrhea 3y agoThat is, a bit like TFA, a tad too high-level for what I was hoping to find here. What's immensely mentally blocking me is how such high-level, admittedly elegant solutions translate down. Async code writes naturally in JS/Node.js, great; but it's imperative code on the lower levels, assembly even. The article mainly defers to libuv. But how does it work? Add to that the different concurrency models. Some runtimes are async and multithreaded (.NET, don't know about JVM, ...), some are single-threaded async (Python, I guess Node.js as well, ...). Some are native code async, which can be either single- or multithreaded (Rust). I'm aware of coroutines and how they do cooperative multitasking, green threads, native threads, ... A large, complex landscape that I have been trying to grok and fit into a unified mental model, but I am slowly losing hope that's even possible. Things seem too heterogeneous. If anyone knows a good resource (preferably book) on this topic, I'd love to hear about it.
- brmgb 3y ago> But how does it work? There clearly is something doing the scheduling and dispatching somewhere in the stack. There will most likely be queues involved and data structures linking receivers and emitters if you are using events. If you use system threads, they might actually be multiple schedulers involved (the OS one and the runtime one). My advice is to pick up a system book about designing OS which will have a section about scheduling and work from there. Runtime schedulers are not that different.
- milesvp 3y agoso at a fundamental level, there’s really nothing other time slicing and interrupts. anything a single cpu does that looks like it’s doing 2 things at once, usually there’s some loop somewhere that is moving data in and out of registers to allow 2 pieces of code to run interleaved. usually it’s doing this many times a second. if the os doesn’t do this, the code running on the os can. then there’s interrupts. if there is no threading loop, you can still register interrupts. this is a fundamental concept in cpu design. every cpu has some way of interrupting the current task and run some function. so you can register an interrupt handler for certain types of interrupts, and that code then is run instead of whatever code is running then when the interrupt is handled it does back to the place it was. It looks like you have non imperative code because 2 things happened close enough to look simultaneous, but its still sliced.