3 ms·
The guarantee you get when using an event loop (or some other cooperative concurrency model) is that inside a contiguous block of code you don't have to worry a
by russell_h 13y ago
The guarantee you get when using an event loop (or some other cooperative concurrency model) is that inside a contiguous block of code you don't have to worry about any other code accessing "your" data: until you yield control, the only code that will execute is your own.
Obviously if you call a function you need to be aware of what it might do to any state you grant it access to, but even then it can only mutate state either before it returns (as in most popular programming models even without concurrency) or, if it registers a callback, at some point after you yield control.
It takes a little bit of getting used to, but this effectively makes mutual exclusion blocks the default unit of execution - and in doing so eliminates a lot of the care needed to write correct code in a preemptive model. Take a look at a well written Node.js or Twisted app - neither Javascript nor Python has concurrent datastructures in their standard library, but it just isn't an issue in most cases.
All of that being said, I think that the failure of this model is that the reason programmers tend to yield control is not because they no longer need it, but rather because you they must do so in order to avoid blocking the event loop on I/O or a timer. In other words, they need to model arithmetic, for example, fundamentally differently than they model loading a configuration file. Things get even wonkier when you start specifying interfaces without knowledge of the implementation: at some point almost any operation could require IO, even if the default implementation doesn't, so you end up registering callbacks on every line. In addition to wreaking havoc on your mental model you have to begin worrying about stack sizes, etc.
In this sense, I think well written Go has a certain elegance. The programmer knows to treat shared state as a special and dangerous case. Instead, you tend to program around a produce/consume model using channels with near zero shared state. You don't need to worry about whether a call yields to the scheduler or not, or even how many threads your goroutines map to - you just write little blocks of code that operate on easily comprehensible state.