> What is harder to find is the bridge between them, the part that connects understanding how async works to actually shipping with it.
There is actually already a tutorial at this level: Tokio has its ‘async in depth’ tutorial [1] that walks you through building a toy runtime and using it to run a future.
Not a complaint — you can never have too many tutorials, unless they're about monads — but just a pointer in case you hadn't seen it :)
Yeah, it also makes it seem like Rust is weird for using Future as the type name, but actually JavaScript (Promise) is the odd one out here.
Probably a JS dev learning Rust without much exposure to other languages
With so much AI-produced junk out there, sites like this one are a breath of fresh air. Simple, aesthetic with real educational value. A second place this week (after https://www.makingsoftware.com/) where I've happily parked a few hours of my life.
I really enjoy the prose of this article. The writing breaks down concepts in a very easily understandable way. Thank you for posting, will definitely finish this one later!
Don't use async but use threads instead. Threading treats the CPU as a resource, which it is! Whereas async simply locks the CPU, which can deplete the system for longer computations.
If you hate garbage collection pauses (which most Rust users do) then don't use async.
You use async to preserve system resources. For example you can easily exhaust the host with ~20k connections running a thread-per-connection schema where each thread simply waits for epoll event, async prevents this by having a threadpool of ~16 threads that handle all the connections instead of polling the scheduler wakes it up, asks "do you haev work to do" if not continues to next task. (This heavily varied by the async runtime implementation, each async runtime can and will act differently to maximize efficiency over throughput)
> You use async to preserve system resources. For example you can easily exhaust the host with ~20k connections running a thread-per-connection schema...
Sure, if you need to run 20k connections then use async. But the fact of the matter is that the vast majority of software is not going to take on 20k connections. Those people (i.e the majority of software devs) should use threads, because they are much easier to reason about and work with.
The whole point of async/await (besides allowing synchronous style code instead of callbacks) is that it lets you yield the thread efficiently back to the executor (OS threads have a massive overhead in comparison). And if you're doing something that blocks the thread instead of yielding it, you can choose between either spawn_blocking or a thread pool.
They are two different models and sometimes one is better than the other. Async tends to win for IO-bound code (due to using less memory for each blocked coroutine) and threads for CPU-bound code.
You can mix and match. For work, I run a conferencing server ... packet processing runs in one thread per core, out of band signalling runs in async on a separate pool of threads. It used to all be async, but using threads for packet processing shaved a little cpu, doing my own timers shaved a little cpu, cpu pinning the threads and the sockets shaved a little cpu, and at the end of the day we shaved a couple instances... too bad instance cost is immaterial compared to bandwidth. Also, it probably drives my coworkers crazy ... but there's really only one spot where it causes issues, so eh, it's fine.
For other things, async task (or green thread, whatevs) per connection is a very nice model that you can't do with thread per connection because I don't think OSes are happy to gave hundreds of thousands of threads.
Your confusing concurrency with parallelism. Async allows one core to switch between many threads of execution that can do work and not stop one thread of execution because it needs to wait for a resource. It's beneficial to use async if you're application is I/O heavy even if it's single threaded.
> Whereas async simply locks the CPUWhereas async simply locks the CPU
This is also completely nonsense, context switching behavior is OS dependent and your average general purpose kernel is not cooperative. You will run for your allotted quanta or reschedule when you run out of coroutines that can execute without waiting for resources.
The problem with all these async things is that people don't understand what's actually happening.
It's just doing a loop and a call to poll(), that's it.
It's way way way less expensive than using threads. Of course you must give control to your main loop every once in a while, so if you have a long computation you either create a thread or split it and return control to your main loop.
It's how all GUI programming has always been done.
> What is harder to find is the bridge between them, the part that connects understanding how async works to actually shipping with it.
There is actually already a tutorial at this level: Tokio has its ‘async in depth’ tutorial [1] that walks you through building a toy runtime and using it to run a future.
Not a complaint — you can never have too many tutorials, unless they're about monads — but just a pointer in case you hadn't seen it :)
[1]: https://tokio.rs/tokio/tutorial/async
Bit weird to have a rust tutorial list JavaScript async as assumed knowledge tbh.
Yeah, it also makes it seem like Rust is weird for using Future as the type name, but actually JavaScript (Promise) is the odd one out here. Probably a JS dev learning Rust without much exposure to other languages
With so much AI-produced junk out there, sites like this one are a breath of fresh air. Simple, aesthetic with real educational value. A second place this week (after https://www.makingsoftware.com/) where I've happily parked a few hours of my life.
I really enjoy the prose of this article. The writing breaks down concepts in a very easily understandable way. Thank you for posting, will definitely finish this one later!
I liked this article because it helped me understand the javacript engine flow better than I had.
The rust flow is so much more natural to me.
Don't use async but use threads instead. Threading treats the CPU as a resource, which it is! Whereas async simply locks the CPU, which can deplete the system for longer computations.
If you hate garbage collection pauses (which most Rust users do) then don't use async.
You use async to preserve system resources. For example you can easily exhaust the host with ~20k connections running a thread-per-connection schema where each thread simply waits for epoll event, async prevents this by having a threadpool of ~16 threads that handle all the connections instead of polling the scheduler wakes it up, asks "do you haev work to do" if not continues to next task. (This heavily varied by the async runtime implementation, each async runtime can and will act differently to maximize efficiency over throughput)
> You use async to preserve system resources. For example you can easily exhaust the host with ~20k connections running a thread-per-connection schema...
Sure, if you need to run 20k connections then use async. But the fact of the matter is that the vast majority of software is not going to take on 20k connections. Those people (i.e the majority of software devs) should use threads, because they are much easier to reason about and work with.
The whole point of async/await (besides allowing synchronous style code instead of callbacks) is that it lets you yield the thread efficiently back to the executor (OS threads have a massive overhead in comparison). And if you're doing something that blocks the thread instead of yielding it, you can choose between either spawn_blocking or a thread pool.
They are two different models and sometimes one is better than the other. Async tends to win for IO-bound code (due to using less memory for each blocked coroutine) and threads for CPU-bound code.
You can mix and match. For work, I run a conferencing server ... packet processing runs in one thread per core, out of band signalling runs in async on a separate pool of threads. It used to all be async, but using threads for packet processing shaved a little cpu, doing my own timers shaved a little cpu, cpu pinning the threads and the sockets shaved a little cpu, and at the end of the day we shaved a couple instances... too bad instance cost is immaterial compared to bandwidth. Also, it probably drives my coworkers crazy ... but there's really only one spot where it causes issues, so eh, it's fine.
For other things, async task (or green thread, whatevs) per connection is a very nice model that you can't do with thread per connection because I don't think OSes are happy to gave hundreds of thousands of threads.
Your confusing concurrency with parallelism. Async allows one core to switch between many threads of execution that can do work and not stop one thread of execution because it needs to wait for a resource. It's beneficial to use async if you're application is I/O heavy even if it's single threaded.
> Whereas async simply locks the CPUWhereas async simply locks the CPU
This is also completely nonsense, context switching behavior is OS dependent and your average general purpose kernel is not cooperative. You will run for your allotted quanta or reschedule when you run out of coroutines that can execute without waiting for resources.
> context switching behavior is OS dependent and your average general purpose kernel is not cooperative.
True, but if all you are using is async, then you're basically back at Windows 3.1 cooperative multitasking, except now within a Rust program.
The problem with all these async things is that people don't understand what's actually happening.
It's just doing a loop and a call to poll(), that's it.
It's way way way less expensive than using threads. Of course you must give control to your main loop every once in a while, so if you have a long computation you either create a thread or split it and return control to your main loop.
It's how all GUI programming has always been done.