4 ms·
"If you want blocking code to run asynchronously, just run it on another task." This highlights one of the main disconnects between "async advocates" and "sync
by jerf 2y ago
"If you want blocking code to run asynchronously, just run it on another task."
This highlights one of the main disconnects between "async advocates" and "sync advocates", which is, when we say something is blocking, what, exactly, is it blocking?
If you think in async terms, it is blocking your entire event loop for some executor, which stands a reasonable chance of being your only executor, which is horrible, so yes, the blocking code is the imposition. Creating new execution contexts is hard and expensive, so you need to preserve the ones you have.
If you think in sync terms, where you have some easy and cheap ability to spawn some sort of "execution context", be it a Haskell spark, an Erlang or Go cheap thread, or even are just in a context where a full OS thread doesn't particularly bother you (a situation more people are in than realize it), then the fact that some code is blocking is not necessarily a big deal. The thing it is blocking is cheap, I can readily get more, and so I'm not so worried about it.
This creates a barrier in communication where the two groups don't quite mean the same thing by "sync" and "async".
I'm unapologetically on Team Sync because I have been programming in contexts where a new Erlang process or goroutine is very cheap for many years now, and I strongly prefer its stronger integration with structured programming. I get all the guarantees about what has been executed since when that structured programming brings, and I can read them right out of the source code.
- winwang 2y agoI like your take, and I propose there is another kind of division: function "coloring". An async "effect" type (e.g. futures, or even just 'async function') signals to programmers that there is some concurrency stuff going on around the place(s) you use it and that we need to talk about how we're going to handle it. i.e. rather than a performance issue, it's a correctness/safety/semantics issue.
- jtrueb 2y ago“This highlights one of the main disconnects between "async advocates" and "sync advocates", which is, when we say something is blocking, what, exactly, is it blocking?” When I have work on a cooperatively scheduled executor for optimal timing characteristics. Sending work/creating a task on a preemptive executor is _expensive_. Furthermore, if that blocking work includes some device drivers with interactions with hardware peripherals, I can’t reasonable place that work on a new executor without invalidating hardware timing requirements. Threads and executors can be infeasible or impossible to spawn. I have 1 preemptive priority and the rest are cooperative on bare metal. I can eat the suboptimal scheduling overhead with a blocking API/RTOS or I need the async version of things.
- dboreham 2y ago> it is blocking your entire event loop for some executor In the browser, only. Which is where all this nonsense began. Browser didn't have threads (Win16) so they invented a crappy cooperative scheme with callbacks, which became gussied up as "async" and now has a cult following. It was all a hack designed to make a terrible runtime environment usable. And now we have a priesthood telling us this is how to program, and if you disagree then you must be stupid. You're correct that we should be using CSP (Erlang, Go, Occam, Actors).
- sweetjuly 2y agoIt's not completely to blame on the browsers. We had the concept of green threads [1] well before that, albeit for equally dubious reasons: OS threads were believed to be too slow, and so green threads were composed onto OS threads. It seems a better choice here would be to try and improve OS threads, but the speed at which various program languages and libraries develop well outstrips anything we could achieve with the kernel, and so maybe this was inevitable :) [1]: https://en.wikipedia.org/wiki/Green_thread https://en.wikipedia.org/wiki/Green_thread
- halostatue 2y agoNote that Erlang processes are essentially green threads within OS thread, but the Erlang process manager is preemptive (unlike most green thread managers). There is room for improvement in both directions.
- vacuity 2y agoOn the topic of improving OS threads, there are scheduler activations and similar mechanisms in some microkernels. Basically sending events for all hardware things and context switches, making user level threads as flexible as kernel threads. Unfortunately they haven't become mainstream.
- nox101 2y ago> If you think in async terms, it is blocking your entire event loop for some executor I think I'm mis-understanding this. async doesn't block your entire event loop. Other events continue to be processed. Trivial example const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); setInterval(() => { console.log(performance.now()); }, 1000); console.log('before'); await wait(10000); console.log('after'); Did I misunderstand your meaning of "blocking the entire event loop"?
- whilenot-dev 2y agoI think GP means that a synchronous blocking call in an async runtime would block and bring the executor (event loop) to a global halt. Think import { readFileSync } from 'node:fs'; async function main() { const data = readFileSync( '/etc/machine-id', { encoding: 'utf-8' }, ); console.log(data); } await main(); VS import { readFile } from 'node:fs'; async function main() { const data = await new Promise((resolve, _reject) => { readFile( '/etc/machine-id', { encoding: 'utf-8' }, (_err, data) => { resolve(data); }, ); }); console.log(data); } await main(); (no error handling to bring the point better across).
- whilenot-dev 2y agoTo add: This issue isn't that troublesome in languages like JavaScript where the runtime is event driven out-of-the-box (synchrounous APIs are considered the exception and suffixed with 'Sync' by convention), but it will become difficult to reason about in languages like rust and python where an event driven runtime is optionally choosen by the developer. Because now it's not enough to conceptually grep all awaiting calls to reason about any blocking code, but you'd also need to know if any other synchronous calls are implicitly awaiting in an underlying layer.
- jerf 2y agoJust to confirm, whilenot-dev had it correct. You can have more than one executor in some implementations (you know you do if you can keep more than one CPU busy), but in all the implementations you're certainly causing problems if you sync-block an executor for long periods of time, that's not what the system is for. By contrast, you want to open a thread/goroutine/Erlang process/Haskell spark/execution context in a threaded language and have it just bang away on numeric computation for a few days? Usually that's just fine, though if you've got a green-threading implementation, double-check first. Most green thread implementations seem to go through a period where that is not fine, but then eventually it is; Go for instance passed through that quite a few versions ago.