4 ms·
> async/await is one product of a long line of thought that says "threads are too hard for programmers to get right". Having used both threads and async/await
by toast0 14d ago
> async/await is one product of a long line of thought that says "threads are too hard for programmers to get right".
Having used both threads and async/await and using them both in the same program, I don't see how async/await is supposed to make it easier to get right.
In my experience, async/await seems to be a solution to avoid running too many threads. In Javascript, because you could only have one thread in browsers; in other languages because thread per X is too many threads and queuing to a thread pool might not be desirable either.
Async/await always feels terrible to use though. Some other way to get thread like semantics without having to have OS threads for everything seems better (to me). Erlang processes, Java Loom Virtual Threads (which I haven't used), etc. If it avoids having all memory shared, even better.
- jerf 14d agoIn the 1990s, threads were programmed with extensive use of semaphores and threads arbitrarily running around shared data structures. This is a disastrous approach to threading, and I agree with pretty much every scathing condemnation written about it. The problem is, the community collectively decided the problem was "threading" in general rather than "trying to have tons of threads running around shared data structures controlled via piles of simultaneously-held semaphores" specifically. If you don't structure your threads on that basis, but instead default to something that looks more like actors and message passing, even if it isn't strictly speaking actors and message passing, the complexity comes down. Add some later elaborations like structured concurrency and a few other pre-canned design patterns for threading like a parallel map or worker pools being issued work items and it becomes merely something difficult rather than insane. When you program with threads sanely, it takes very little for async/await to actually be the substantially more complicated and difficult-to-understand choice when you have a workflow more interesting than "always await everything immediately" to implement, to say nothing of how nice it is to have things actually running on multiple cores simultaneously without having to carefully arrange for it.
- rerdavies 14d agoThe principal difference between dispatching in a thread-based framework and async/await is that async/await allows you to program sequences of asynchronous operations much more easily. No more separation of code that initiates an async operation and the code that handles the result!
- switchbak 14d agoThese higher level primitives that they mention provide the primitives for exactly that. This can be found in other paradigms besides async/await. I do find that the ergonomics of this are highly dependent on a few features of a language runtime, without which it all falls apart. Or you need language specific syntax and typically a single standard implementation.
- rerdavies 14d agoOf course. I don't think a language can support async/await without a library implementation, or the language features that support it. And I can't honestly think of another paradigm that doesn't require callback functions or lambdas that, ergonomically, end up producing function implementations that end up drifting off the right side of the screen for anything more than a couple of sequential asynchronous operations.
- switchbak 14d agoI didn’t say async/await need language features (though they usually do), I meant that high level async orchestration can be represented with suitable language features - often in ways that are more interesting than async/await. “I can't honestly think of another paradigm that doesn't require callback functions” … Haskell, Scala, Rust all use various approaches to asynchrony that leverage these language features to provide you very usable abstractions without the “callbacks” you mention. Some of those lean on lambdas, but the scrolling off the right issue hasn’t been an issue there for at least a decade now. Scala’s direct mode is interesting, as an example of library driven, blocking/imperative style interactions that provide most of the benefits of the monadic effect style, but in a way that’s far easier for a human to write and review. This might not be ready for mass adoption yet, but I think it’s a sneak peek of where we’ll see some languages move to.
- theamk 14d agoLinux has 8MB thread stacks by default, Windows apparently has 1MB ones, and that space is not going anywhere. As long as people are worried about memory, they will need something lighter than threads. (Yes, golang managed to create dynamic stacks, but this required major support from compiler and so unlikely to appear in existing languages). Also, I think that single-threaded programs, even with co-routines, are just so much nicer than multi-threaded ones. You write "index = last_index++;" and it Just Works (tm), no need to worry about locks or atomics or other thread access.
- mitxela 14d agoBoth windows and Linux let you configure the thread stack size
- PhilipRoman 14d agoJust to nitpick, it's a 8MB-sized mapping, not 8MB of RAM.
- e4m2 14d agoLikewise, on Windows, it's 1 MB of reserved memory but only 4 KB of initially committed memory. https://learn.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations https://learn.microsoft.com/en-us/cpp/build/reference/stack-...
- spinningslate 14d ago> Yes, golang managed to create dynamic stacks, but this required major support from compiler and so unlikely to appear in existing languages That's true but I'm puzzled by the decision rationale. It's undeniably a major undertaking to add first class, fine-grained processes to a language and its runtime. But time invested there gets the multiplicative upside that all language users benefit from the investment. Instead, Async/Await transfers the complexity to users of the language, as TFA describes. As an Erlang and now gleam developer, I'm continuously grateful for the BEAM's support for fine-grained processes (note these are VM processes, not OS level). If I want to do things in parallel, I spawn a new process to do it. Do I want that concurrency because of io latency or parallel computation? Doesn't matter. Processes handle both. If I want an actor - a long(ish) lived "object" that responds to messages sent to it - I spawn it as a process. If I want to communicate between processes, I send a message. That's the only choice. No shared memory so no semaphores, locks and whatnot. I never have to think "hmm, should this function be sync or async?" and reason about the transitive implications through the entire call stack. I write functions to calculate values. If I want function A to be called after function B in program 1, I write them sequentially. If I want to run them concurrently in program 2, I spawn them in separate processes. Concurrency is a decision at the calling site, not when writing the function being called. One concurrency primitive that meets all the needs. The reduction in cognitive load is palpable compared to Python (the other language I use regularly). The usual reaction is "yeah but performance". I've never found this to be an issue in real life. Sure there are benchmarks that show C/Rust/C#/whatever is faster, often meaningfully so. In practice, for my needs: never been a problem. I'm ever more grateful for the elegance and consistency of the BEAM concurrency model. From an ergonomic perspective, Async/Await feels like a poor abstraction by comparison. That's not to say the BEAM (or its languages) is the final word in concurrency. The strong encapsulation boundaries from Structured Concurrency[0] would be a useful addition. Though even there, Erlang's supervisor hierarchies provide a a similar mechanism. Dataflow is another interesting area (many task-concurrent design questions are essentially dataflow problems). Even without improvement though I'd still take Erlang's approach over Async/Await every day. [0] https://en.wikipedia.org/wiki/Structured_concurrency https://en.wikipedia.org/wiki/Structured_concurrency