Rust Async WG member here: This question comes up pretty regularly when people first learn about async Rust. I suspect it’s because performance is often emphasized as the main benefit of async Rust.<p>In my opinion performance is always relative and dependent on the workload. Instead it’s worth looking more at which capabilities async Rust provides which aren’t present in non-async Rust. This includes ad-hoc cancellation, ad-hoc concurrency, and combining the two into things such as timeouts.<p>I wrote a post explaining this in detail a few months ago: <a href="https://blog.yoshuawuyts.com/why-async-rust/" rel="nofollow">https://blog.yoshuawuyts.com/why-async-rust/</a>
You can multiplex lightweight threads or async over kernel threads.<p>If you're serving a single connection with 1 thread then you're wasting that thread unless you do enough computation to fill that logical hardware thread's capacity.<p>In other words it's a good idea to use both together.<p>I wrote a 1:M:N scheduler in Rust, C and Java. It multiplexes N lightweight threads over M kernel threads and has 1 scheduler thread.<p><a href="https://GitHub.com/samsquire/preemptible-thread">https://GitHub.com/samsquire/preemptible-thread</a><p>Nginx has multiple worker processes and an event loop in each I think.<p>There's no reason your event loop couldn't be on each thread.<p>I'm also thinking of separating recv and send on different threads so you can send and receive simultaneously.
For me it comes down to simplicity of the sharing model. With async you can basically ignore sharing and write stuff that works against the same data structures because you know they’re mutually exclusive. Once you move stuff into separate threads you need to be a lot more careful, using Arc and Mutex et al, or devise channel semantics. I tend to only use threads when I never want a future interfering with the scheduling of another future, either when one is really long or when low latency is absolutely critical. One example recently was I had very low latency network stuff happening on one thread and it was sending messages to a TUI thread. I absolutely didn’t want the networking code ever blocked by the TUI, even for short times.<p>I find it fascinating that LWT have come so far that this is a legitimate question. There was a time when async << threads << processes. But a lot of heroic good work went into bringing about this wonderful age.
From my limited experience, passing variables around between threads is quite painful in Rust, async makes sense therefore as it allows you to reduce shared ownership of variables. A while ago I wrote a simple multi-threaded networking library in Rust and things that would be trivial in C/C++, like accepting a network connection and passing the socket handle to a thread with some extra information were really tricky (maybe just because I was inexperienced in Rust) and took some quite ugly code to solve. In the end the program I produced had significant performance bottlenecks and was quite hard to understand.<p>I rewrote the same thing in Golang afterwards, took me maybe 10 % of the time and the code I produced was very readable and extremely fast. After that I'm a little burnt regarding Rust and have a hard time buying all the hype around it. I guess it has potential for embedded systems and other memory-constrained environments though, as Golang can't compete there right now due to the large size of the runtime, though there are projects like Tinygo that might eventually fill that gap as well. So I'm probably more hyped about Zig than Rust for now when it comes to next-gen languages.
Main reason for me to use async instead of threads is when I have a very high number of concurrent connections, that don’t do much in terms of CPU, but will require a lot of RAM because of the minimum stack size per thread. If I don’t need that, then threads are fine.
Am I wrong for following the general rule of thumb:<p>"If your tasks spend most of their time waiting, use async. If they spend most of their time running, use threads"<p>?
I think async concurrent code is just much easier to reason about, regardless of threading. Also, for intra-request concurrency (e.g. i need to grab data from N different sources to complete this request), it's much easier to arrange for that work to be done concurrently. Also easier to implement highly responsive applications with async vs. threading.
Personally have found async rust to be both ugly and not very useful. Instead of async IO I just make my own event loop with mio and use non-blocking non-async libraries (like tungstenite for WS and mio_httpc for HTTP).