diff --git a/src/SUMMARY.md b/src/SUMMARY.md index daa9a07af7..60680d1983 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -102,8 +102,8 @@ - [Extensible Concurrency with the `Sync` and `Send` Traits](ch16-04-extensible-concurrency-sync-and-send.md) - [Async and Await](ch17-00-async-await.md) - - [Futures and the Async Syntax](ch17-01-tasks.md) - - [something something tokio?!?](ch17-02.md) + - [Futures and the Async Syntax](ch17-01-futures-and-syntax.md) + - [Concurrency With Async](ch17-02-concurrency-with-async.md) - [Object Oriented Programming Features of Rust](ch18-00-oop.md) - [Characteristics of Object-Oriented Languages](ch18-01-what-is-oo.md) diff --git a/src/ch17-01-tasks.md b/src/ch17-01-futures-and-syntax.md similarity index 100% rename from src/ch17-01-tasks.md rename to src/ch17-01-futures-and-syntax.md diff --git a/src/ch17-02-concurrency-with-async.md b/src/ch17-02-concurrency-with-async.md new file mode 100644 index 0000000000..5e86b00fde --- /dev/null +++ b/src/ch17-02-concurrency-with-async.md @@ -0,0 +1,191 @@ +## Concurrency With Async + +In this section, we will apply async to some of the same concurrency challenges +we tackled with threads in chapter 16. Since we already talked about a lot of +the key ideas there, in this section we will focus on what is different between +threads and futures. + +In many cases, the APIs for working with concurrency using async are very similar to those for using threads. In other cases, they end up being shaped fairly differently. Even when the APIs look similar, they often have different behavior and they nearly always have different performance characteristics. + +### Counting + +The first task we tackled in Chapter 16 was counting up on two separate threads. +Let’s do the same using async. The `trpl` crate supplies a `spawn_task` function +which looks very similar to the `thread::spawn` API, and a `sleep` function +which is an async version of the `thread::sleep` API. We can use these together +to implement the same counting example as with threads. + +To start, we will set up our `main` function with `trpl::block_on`: + +```rust +{{#rustdoc_include ../listings/ch17-async-await/listing-TODO-01/src/main.rs:block_on}} +``` + +Then we can write two loops within that block, each with a `trpl::sleep` call in +them. Similar to the threading example, we put one in the body of a +`trpl::spawn_task`, just like we did with `thread::spawn`, and the other in a +top-level `for` loop. Notice that we also need to add a `.await` after the +`sleep` calls. + +```rust +{{#rustdoc_include ../listings/ch17-async-await/listing-TODO-01/src/main.rs:task}} +``` + +Putting that all together, we end up with the code in Listing 17-TODO: + ++ +```rust +{{#rustdoc_include ../listings/ch17-async-await/listing-TODO-01/src/main.rs:all}} +``` + + + +This does something very similar to what the thread-base implementation did, as +we can see from the output when we run it. (As with the threading example, you +may see a different order in your own terminal output when you run this.) + +```text +hi number 1 from the second task! +hi number 1 from the first task! +hi number 2 from the first task! +hi number 2 from the second task! +hi number 3 from the first task! +hi number 3 from the second task! +hi number 4 from the first task! +hi number 4 from the second task! +hi number 5 from the first task! +``` + +This stops as soon as the for loop in the body of the main async block finishes, +because the task spawned by `spawn_task` is shut down when the main function +ends—just like threads are. Thus, if you want to run all the way to the +completion of the task, you will need to use a join handle to wait for the first +task to complete. With threads, we used the `join` method to “block” until the +thread was done running. Here, we can use `await` to do the same thing: + ++ +```rust +{{#rustdoc_include ../listings/ch17-async-await/listing-TODO-02/src/main.rs}} +``` + + + +Now the output again looks like what we saw in the threading example. + +```text +hi number 1 from the second task! +hi number 1 from the first task! +hi number 2 from the first task! +hi number 2 from the second task! +hi number 3 from the first task! +hi number 3 from the second task! +hi number 4 from the first task! +hi number 4 from the second task! +hi number 5 from the first task! +hi number 6 from the first task! +hi number 7 from the first task! +hi number 8 from the first task! +hi number 9 from the first task! +``` + +So far, it looks like async and threads basically give us the same basic +behavior. However, there are a few important differences already. One was using +`.await` instead of calling `join` on the join handle. Another is that we needed +to await both `sleep` calls. Most importantly, though, we did not need to spawn +another operating system thread to do this. We were able to get concurrency for +just the cost of a task, which has much faster startup time and uses much less +memory than an OS thread. + +What is more, we actually do not need the `spawn_task` call at all to get +concurrency here. Remember that each async block compiles to an anonymous +future. That means we can put each of these two loops in an async block and then +ask the runtime to run them both to completion using `trpl::join`: + ++ +```rust +{{#rustdoc_include ../listings/ch17-async-await/listing-TODO-03/src/main.rs}} +``` + + + +When we run this, we see both futures run to completion: + +```text +hi number 1 from the first task! +hi number 1 from the second task! +hi number 2 from the first task! +hi number 2 from the second task! +hi number 3 from the first task! +hi number 3 from the second task! +hi number 4 from the first task! +hi number 4 from the second task! +hi number 5 from the first task! +hi number 6 from the first task! +hi number 7 from the first task! +hi number 8 from the first task! +hi number 9 from the first task! +``` + +Here, you will see the exact same order every time, which is very different from +what we saw with threads. That is because the `trpl::join` function is *fair*, +meaning it checks both futures equally, rather than letting one race ahead. With +threads, the operating system decides which thread to check, and that is +ultimately out of our control. With an async runtime, the runtime itself decides +which future to check, so it has the final say. In practice, the details get +complicated because an async runtime might use operating system threads under +the hood as part of how it manages concurrency, but a runtime can still choose +to guarantee fairness even so. However, runtimes do not have to guarantee +fairness for any given operation, and even within a given runtime, different +APIs sometimes exist to let you choose whether fairness is something you care +about as a caller. + +Try some of these different variations on awaiting the futures and see what they +do. For an extra challenge, see if you can figure out what the output will be +*before* running the code! + +* Remove the async block from around either or both of the loops. +* Await each async block immediately after defining it. +* Wrap only the first loop in an async block, and await the resulting future + after the body of second loop. + +### Tasks vs. Threads + + + +### Async Move Blocks + +In Chapter 13, we learned how to use the `move` keyword with closures, and in +Chapter 16, we saw that we often need to use closures marked with `move` when +working with threads. The `move` keyword also works with async blocks, which +also sometimes need to take ownership of the data they reference. Remember, any +time you write a future, a runtime is ultimately responsible for executing it. +That means that an async block might outlive the function where you write it, the +same way a closure does—even if you do not pass it to a closure explicitly. + +> Note: the `async` keyword does not yet work with closures directly, so you +> cannot write code like these function calls: +> +> ```rust,ignore +> example_1(async || { ... }); +> example_2(async move || { ... }); +> ``` +> +> However, since async blocks themselves can be marked with `move`, this ends up +> not being a problem. Remember that `async` blocks compile to anonymous +> futures. That means you can write calls like this instead: +> +> ```rust,ignore +> example_1(|| async { ... }); +> example_2(|| async move { ... }); +> ``` +> +> These closures now return anonymous futures, meaning they work basically the +> same way that an async function does. + +### Message Passing + +Sharing data between futures will look familiar. We can again use async versions +of Rust’s types for diff --git a/src/ch17-02.md b/src/ch17-02.md deleted file mode 100644 index 0200abe2ce..0000000000 --- a/src/ch17-02.md +++ /dev/null @@ -1,141 +0,0 @@ -## TODO: Section Title - -Back in Chapter 16, we saw an example of counting up on multiple threads. Given -that threads allow us to implement a form of concurrency, we might expect that -async APIs can substitute fairly directly for the threading APIs. The `trpl` -crate supplies a `spawn_task` function which looks very similar to the -`thread::spawn` API, and a `sleep` function which is an async version of the -`thread::sleep` API. We can use these together to implement the same counting -example as with threads. - -To start, we will set up our `main` function with `trpl::block_on`: - -```rust -{{#rustdoc_include ../listings/ch17-async-await/listing-TODO-01/src/main.rs:block_on}} -``` - -Then we can write two loops within that block, each with a `trpl::sleep` call in -them. Similar to the threading example, we put one in the body of a -`trpl::spawn_task`, just like we did with `thread::spawn`, and the other in a -top-level `for` loop. Notice that we also need to add a `.await` after the -`sleep` calls. - -```rust -{{#rustdoc_include ../listings/ch17-async-await/listing-TODO-01/src/main.rs:task}} -``` - -Putting that all together, we end up with the code in Listing 17-TODO: - -- -```rust -{{#rustdoc_include ../listings/ch17-async-await/listing-TODO-01/src/main.rs:all}} -``` - - - -This does something very similar to what the thread-base implementation did, as -we can see from the output when we run it: - -```text -hi number 1 from the second task! -hi number 1 from the first task! -hi number 2 from the first task! -hi number 2 from the second task! -hi number 3 from the first task! -hi number 3 from the second task! -hi number 4 from the first task! -hi number 4 from the second task! -hi number 5 from the first task! -``` - -Just like with the threading example, you may see a different order in your own -terminal output when you run this. And, just like the threading example, if you -want to run all the way to the completion of the first task, you will need to -use a join handle to wait for the first task to complete. With threads, we used -the `join` method to “block” until the thread was done running. Here, we can use -`await` to do the same thing: - -- -```rust -{{#rustdoc_include ../listings/ch17-async-await/listing-TODO-02/src/main.rs}} -``` - - - -Now the output again looks like what we saw in the threading example. - -```text -hi number 1 from the second task! -hi number 1 from the first task! -hi number 2 from the first task! -hi number 2 from the second task! -hi number 3 from the first task! -hi number 3 from the second task! -hi number 4 from the first task! -hi number 4 from the second task! -hi number 5 from the first task! -hi number 6 from the first task! -hi number 7 from the first task! -hi number 8 from the first task! -hi number 9 from the first task! -``` - -So far, it looks like async and threads basically give us the same basic -behavior. However, there are a few important differences already. One was using -`.await` instead of calling `join` on the join handle. Another is that we needed -to await both `sleep` calls. Most importantly, though, we did not need to spawn -another operating system thread to do this. We were able to get concurrency for -just the cost of a task, which has much faster startup time and uses much less -memory than an OS thread. - - - -What is more, we actually do not need the `spawn_task` call at all to get -concurrency here. Remember that each async block compiles to an anonymous -future. That means we can put each of these two loops in an async block and then -ask the runtime to run them both to completion using `trpl::join`: - -- -```rust -{{#rustdoc_include ../listings/ch17-async-await/listing-TODO-03/src/main.rs}} -``` - - - -When we run this, we see both futures run to completion: - -```text -hi number 1 from the first task! -hi number 1 from the second task! -hi number 2 from the first task! -hi number 2 from the second task! -hi number 3 from the first task! -hi number 3 from the second task! -hi number 4 from the first task! -hi number 4 from the second task! -hi number 5 from the first task! -hi number 6 from the first task! -hi number 7 from the first task! -hi number 8 from the first task! -hi number 9 from the first task! -``` - -Here, at least with the implementation we are using in `trpl`, you will see the -exact same order every time, which is very different from what we saw with -threads. That is because the `trpl::join` function is *fair*, meaning it checks -both futures at an equal rate. With threads, the operating system decides which -thread to check, and that is ultimately out of our control. With an async -runtime, the runtime itself decides which future to check, so it has the final -say. In practice, the details get complicated because an async runtime might use -operating system threads under the hood as part of how it manages concurrency, -but a well-designed runtime can still guarantee fairness. - -Try combining different places to await the futures and see what they do. See if you can figure out what the output will be *before* running the code! - -* Remove the async block from around either or both of the loops. -* Await each async block immediately after defining it. -* Wrap only the first loop in an async block, and await the resulting future - after the body of second loop.