From 7f8764ac379a22a0ad428b02c083b110895d81ba Mon Sep 17 00:00:00 2001 From: Julio Biason Date: Fri, 26 Aug 2022 17:29:22 -0300 Subject: [PATCH] Reordering --- .../a-mental-models-for-async-rust.md | 80 ++++++++++++++++--- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/content/thoughts/a-mental-models-for-async-rust.md b/content/thoughts/a-mental-models-for-async-rust.md index 3452e6b..cc73365 100644 --- a/content/thoughts/a-mental-models-for-async-rust.md +++ b/content/thoughts/a-mental-models-for-async-rust.md @@ -15,13 +15,71 @@ It took me awhile to figure out a mental model for doing it right. -## A problem with naming +## But first... + +Before jumping into the model, I need to throw some other concepts that lead to +this model. It may seem a bit not related to async, but it will make sense in +the end (I hope 🙂). + +### Windows 3 and Cooperative multitasking + +People may not remember, but there was a magical time on the old DOS days when +you could not only have a graphical interface, but you could ALSO run more than +one application at the same time. + +That was done in a "shell" called... Windows. + +But Windows 3 did not run like Windows does today. Today, every process +"compete" against each other to have some time running. The OS let an +application run for some time, pauses it, unpauses another, let it run for some +time, pauses, switches to another, and so on. + +But Windows 3 had a different method to give time to different applications: +Instead of the OS saying "your time is over" the application itself must say +"Hey OS, if there is another process that wants to run, I yield my own +execution". + +Besides the application saying that they allow another application to run, the +OS have some control points for them to yield control, in the I/O calls. While +one application is waiting for a read or write to complete, either on disk or +socket, the OS would take care of when the operation completed and then return +control to the application. + +### `epoll()` -I think my initial problem started with naming. The concept of async/await is -quite recent, but for a long time we've been talking about "greenthreads" and -"light-weight threads" -- "threads" that are managed by the application and not -the OS. While there are some differences between greenthreads and async things, -the naming stuck with me (and I *think* I saw some posts linking the two). +### "Greenthreads" + +I think my initial problem started with naming. The concept of async/await +constructs is quite recent, but for a long time we've been talking about +"greenthreads" and "light-weight threads" -- "threads" that are managed by the +application and not the OS. While there are some differences between +greenthreads and async things, the naming stuck with me (and I *think* I saw +some posts linking the two). + +### Rust and threads + +Let's talk about Rust memory model for a second here. Rust emphasises memory +protection by giving regions of memory to owners; a region can only be owner by +one and only one owner and when the owner goes away, the memory is freed. +Nothing fancy. + +This is particularly awesome when you're dealing with multi-threaded code, as a +region of memory can only belong to a price of code in one thread and other +threads can't mess with it. I think the greatest analogy for this behavior is +the explanation Miko Matsakis, which explained this using gifts: + +Imagine you have a gift. You want to give it to someone, but while it is in +your hands, you can unwrap, replace the bow and paper, anything. But once you +*give* it to someone else's, that's their gift; it is up to them to unwrap, +replace bow and paper and whatnot. + +The process of writing code in Rust with that control is sometimes annoying +when you're dealing with code that is not multi-threaded, but when you think +"What would happen if I run this in a separate thread?", then all the complains +make sense. You can even understand that the Rust compiler will not complain +when you end your main code in a loop or a `.join()`. + +## A problem with naming Still on naming, [Tokio](https://tokio.rs/), the most popular async framework in Rust, uses `task::spawn` to spawn a new task, which is pretty close to the @@ -36,7 +94,7 @@ with async was with Rust, so... ## A problem with structure -So you get this "async is thread" mentality due aproximation. And then you try +So you get this "async is thread" mentality due approximation. And then you try to build something async using the same model. For example, a producer/consumer in Rust would be something like: @@ -107,7 +165,7 @@ the task. ## I saw multitasking correctly -One thing I believe I did right was to "mentalize" the way the event loop works +One thing I believe I did right was to "metallize" the way the event loop works akin to Windows 3.11, which was really prone to become completely unresponsive from time to time. The reason for that is that the event loop keeps running things till someone says "Ok, I give up my time and someone else can run their @@ -119,4 +177,8 @@ this is done in I/O layer, for one simple reason: Your code would, normally, block on those, and the event loop will take care of running this in a non-blocking fashion. -## +## So, what the model, anyway? + +You see, seeing task as threads is not the right thing to do. The way that made +everything make sense was to see tasks as **the elements being added to the +MPSC channel**.