6 ms·
Supporting async isn’t a bad thing. It is annoying when working with async stuff and suddenly a library doesn’t support futures or streams. But I do agree that
by andrewzah 5y ago
Supporting async isn’t a bad thing. It is annoying when working with async stuff and suddenly a library doesn’t support futures or streams.
But I do agree that it seems that people jump to use async instead of e.g. threads. I think there should be more literature around what situations work best for threads vs async, etc.
- dodobirdlord 5y agoI think in a lot of cases the async programming model is easier to work with than the thread programming model. In a sense it’s declarative vs procedural programming over again. With threads you have to identify parallelism opportunities at write time, and if you plan poorly you can end up with low core efficiency. With async programming you break your execution up into a graph of inputs and outputs and let your async scheduler dynamically bin-pack everything, paying an overhead for consistently high core efficiency without having to think about it or change your code very much.
- kevincox 5y agoI always thought this, but people don't seem to find the convenience of async as a strong selling point over threads. There isn't a fundamental reason, but doing something like: thing1_promise = get_thing(1) thing2_promise = get_thing(2) thing1 = thing1_promise.await thing2 = thing1_promise.await is almost always more difficult to do with threads. Sure, some threading APIs support joining nicely but it tends to be more awkward. Especially if you want to do something like handle the first request to finish first. Also the startup cost of threads affects how you write code. Does your library spawn a new thread for every HTTP request? Probably not, it is expensive and the server may be on the same host. So you end up with less consistent async support. Since async programming is a lot cheaper you tend to see better support which adds up.
- nandhinianand 5y agoThat's a very intriguing metaphor.. and reminds me of the optimization manually vs using `gcc -O2` .. I am wondering if the "pre-mature optimization is the root of evil" quote applies here and we should all just use async first and only go for threaded/multi-processing programming if there seems to be a performance issue for the business application.
- dodobirdlord 5y agoIt’s probably very painful to try to convert a piece of async software to use a threaded model (though I’m just guessing, having never had to attempt this), so I’d say it’s more a matter of thinking up front about whether you need to control threads directly. But to your main point, I agree. I suspect the vast majority of software has no need for thread-model parallelism, and languages have only historically presented that parallelism model because it’s the model that the OS exposes to userland, even though the OS scheduler is async-style scheduling the machine threads themselves!
- Nullabillity 5y agoYou might not even need to do that, most async runtimes support some kind of middle ground where you take explicit control over which tasks are scheduled to which threads.
- Nullabillity 5y agoIt's also useful as documentation even in single-tasked programs, since async ends up as a "good-enough" IO monad. Then again, few programs are actually single-tasked in practice, and libraries often end up taking advantage of those async primitives for doing stuff like connection pooling even if your application code doesn't see it.
- amelius 5y agoImho, the language should abstract away the difference between threads and async.
- chrismorgan 5y agoThis is quite impossible for a language in Rust’s space; it would violate Rust’s principle of only paying for what you use, because there is necessarily runtime overhead in providing such an abstraction.
- tjoff 5y agoThe compiler would know that in advance though? Thus you only pay for it if it is required.
- chrismorgan 5y agoThreads and async have different restrictions and costs. No shared abstraction is possible for a language like Rust without losing what makes it Rust. Minimal-cost threading support requires taking Send + Sync into account, essentially thread-safety: and the thread-safe primitives are more expensive than the thread-local primitives. Send + Sync matters become pervasive through the design of threaded code, even if they don’t need to be spelled out most of the time. Minimal-cost async requires compiling the code into a state machine, which means you’ve got to restrict borrowing across suspend points, or mess around with Pin. Threading and async are quite different in a language like Rust. They can even play quite nicely together. But you really can’t unify them. Sure, you could do simple cases like single-threaded blocking-or-async, e.g. `async? fn foo() { bar().await; }` compiling to something like strawman `fn sync#foo() { sync#bar(); } async fn async#foo() { async#bar().await; }`, but this falls apart completely as soon as you want to do anything beyond immediate .await.
- amelius 5y ago> it would violate Rust’s principle of only paying for what you use I guess I'm glad that Rust developers don't design CPUs, as we wouldn't have memory abstractions like cache coherent NUMA (which doesn't come for free).