diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /src/doc/book/nostarch/chapter16.md | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/doc/book/nostarch/chapter16.md')
-rw-r--r-- | src/doc/book/nostarch/chapter16.md | 1268 |
1 files changed, 1268 insertions, 0 deletions
diff --git a/src/doc/book/nostarch/chapter16.md b/src/doc/book/nostarch/chapter16.md new file mode 100644 index 000000000..1f404f84d --- /dev/null +++ b/src/doc/book/nostarch/chapter16.md @@ -0,0 +1,1268 @@ +<!-- DO NOT EDIT THIS FILE. + +This file is periodically generated from the content in the `/src/` +directory, so all fixes need to be made in `/src/`. +--> + +[TOC] + +# Fearless Concurrency + +Handling concurrent programming safely and efficiently is another of Rust’s +major goals. *Concurrent programming*, where different parts of a program +execute independently, and *parallel programming*, where different parts of a +program execute at the same time, are becoming increasingly important as more +computers take advantage of their multiple processors. Historically, +programming in these contexts has been difficult and error prone: Rust hopes to +change that. + +<!-- Concurrent programming isn't necessarily helped by having multiple +processors. How I've been teaching it is to distinguish the two by their +workload: concurrent programming serves the needs of I/O-bound workloads and +parallel programming serves the needs of CPU-bound workloads. If you give +CPU bound workloads more CPUs, you have the opportunity to possibly go faster +(assuming sufficient parallelism in the code). For I/O-bound workloads, +rather than the need to have multiple processors, you need to be able to +get as many I/O requests in flight and being processed as you can. This +allows more I/O requests, and as a result better throughput/response time +on those I/O requests. + +We could introduce these concepts and then simplify like we do in a bit to +say that the design considerations of Rust allow both concurrency and +parallelism to be done safely (...and for the remainder of the chapter talk +about those design considerations rather than the specifics for either +concurrency or parallelism) /JT --> +<!-- I really don't want to get in the weeds on this because there are many +other books and resources about concurrency and parallelism because these +concepts aren't Rust specific. I want this to feel accessible to programmers +who have never even considered whether their programs are I/O or CPU bound, +because those are the types of programmers we want to empower (and make them +feel empowered to create concurrent and/or parallel code) through Rust. So I'm +deliberately choosing not to change anything here. /Carol --> + +Initially, the Rust team thought that ensuring memory safety and preventing +concurrency problems were two separate challenges to be solved with different +methods. Over time, the team discovered that the ownership and type systems are +a powerful set of tools to help manage memory safety *and* concurrency +problems! By leveraging ownership and type checking, many concurrency errors +are compile-time errors in Rust rather than runtime errors. Therefore, rather +than making you spend lots of time trying to reproduce the exact circumstances +under which a runtime concurrency bug occurs, incorrect code will refuse to +compile and present an error explaining the problem. As a result, you can fix +your code while you’re working on it rather than potentially after it has been +shipped to production. We’ve nicknamed this aspect of Rust *fearless* +*concurrency*. Fearless concurrency allows you to write code that is free of +subtle bugs and is easy to refactor without introducing new bugs. + +> Note: For simplicity’s sake, we’ll refer to many of the problems as +> *concurrent* rather than being more precise by saying *concurrent and/or +> parallel*. If this book were about concurrency and/or parallelism, we’d be +> more specific. For this chapter, please mentally substitute *concurrent +> and/or parallel* whenever we use *concurrent*. + +Many languages are dogmatic about the solutions they offer for handling +concurrent problems. For example, Erlang has elegant functionality for +message-passing concurrency but has only obscure ways to share state between +threads. Supporting only a subset of possible solutions is a reasonable +strategy for higher-level languages, because a higher-level language promises +benefits from giving up some control to gain abstractions. However, lower-level +languages are expected to provide the solution with the best performance in any +given situation and have fewer abstractions over the hardware. Therefore, Rust +offers a variety of tools for modeling problems in whatever way is appropriate +for your situation and requirements. + +Here are the topics we’ll cover in this chapter: + +* How to create threads to run multiple pieces of code at the same time +* *Message-passing* concurrency, where channels send messages between threads +* *Shared-state* concurrency, where multiple threads have access to some piece + of data +* The `Sync` and `Send` traits, which extend Rust’s concurrency guarantees to + user-defined types as well as types provided by the standard library + +## Using Threads to Run Code Simultaneously + +In most current operating systems, an executed program’s code is run in a +*process*, and the operating system will manage multiple processes at once. +Within a program, you can also have independent parts that run simultaneously. +The features that run these independent parts are called *threads*. For +example, a web server could have multiple threads so that it could respond to +more than one request at the same time. + +Splitting the computation in your program into multiple threads to run multiple +tasks at the same time can improve performance, but it also adds complexity. +Because threads can run simultaneously, there’s no inherent guarantee about the +order in which parts of your code on different threads will run. This can lead +to problems, such as: + +* Race conditions, where threads are accessing data or resources in an + inconsistent order +* Deadlocks, where two threads are waiting for each other, preventing both + threads from continuing +* Bugs that happen only in certain situations and are hard to reproduce and fix + reliably + +Rust attempts to mitigate the negative effects of using threads, but +programming in a multithreaded context still takes careful thought and requires +a code structure that is different from that in programs running in a single +thread. + +Programming languages implement threads in a few different ways, and many +operating systems provide an API the language can call for creating new +threads. The Rust standard library uses a *1:1* model of thread implementation, +whereby a program uses one operating system thread per one language thread. +There are crates that implement other models of threading that make different +tradeoffs to the 1:1 model. + +### Creating a New Thread with `spawn` + +To create a new thread, we call the `thread::spawn` function and pass it a +closure (we talked about closures in Chapter 13) containing the code we want to +run in the new thread. The example in Listing 16-1 prints some text from a main +thread and other text from a new thread: + +Filename: src/main.rs + +``` +use std::thread; +use std::time::Duration; + +fn main() { + thread::spawn(|| { + for i in 1..10 { + println!("hi number {} from the spawned thread!", i); + thread::sleep(Duration::from_millis(1)); + } + }); + + for i in 1..5 { + println!("hi number {} from the main thread!", i); + thread::sleep(Duration::from_millis(1)); + } +} +``` + +Listing 16-1: Creating a new thread to print one thing while the main thread +prints something else + +Note that when the main thread of a Rust program completes, all spawned threads +are shut down, whether or not they have finished running. The output from this +program might be a little different every time, but it will look similar to the +following: + +``` +hi number 1 from the main thread! +hi number 1 from the spawned thread! +hi number 2 from the main thread! +hi number 2 from the spawned thread! +hi number 3 from the main thread! +hi number 3 from the spawned thread! +hi number 4 from the main thread! +hi number 4 from the spawned thread! +hi number 5 from the spawned thread! +``` + +The calls to `thread::sleep` force a thread to stop its execution for a short +duration, allowing a different thread to run. The threads will probably take +turns, but that isn’t guaranteed: it depends on how your operating system +schedules the threads. In this run, the main thread printed first, even though +the print statement from the spawned thread appears first in the code. And even +though we told the spawned thread to print until `i` is 9, it only got to 5 +before the main thread shut down. + +If you run this code and only see output from the main thread, or don’t see any +overlap, try increasing the numbers in the ranges to create more opportunities +for the operating system to switch between the threads. + +### Waiting for All Threads to Finish Using `join` Handles + +The code in Listing 16-1 not only stops the spawned thread prematurely most of +the time due to the main thread ending, but because there is no guarantee on +the order in which threads run, we also can’t guarantee that the spawned thread +will get to run at all! + +We can fix the problem of the spawned thread not running or ending prematurely +by saving the return value of `thread::spawn` in a variable. The return type of +`thread::spawn` is `JoinHandle`. A `JoinHandle` is an owned value that, when we +call the `join` method on it, will wait for its thread to finish. Listing 16-2 +shows how to use the `JoinHandle` of the thread we created in Listing 16-1 and +call `join` to make sure the spawned thread finishes before `main` exits: + +Filename: src/main.rs + +``` +use std::thread; +use std::time::Duration; + +fn main() { + let handle = thread::spawn(|| { + for i in 1..10 { + println!("hi number {} from the spawned thread!", i); + thread::sleep(Duration::from_millis(1)); + } + }); + + for i in 1..5 { + println!("hi number {} from the main thread!", i); + thread::sleep(Duration::from_millis(1)); + } + + handle.join().unwrap(); +} +``` + +Listing 16-2: Saving a `JoinHandle` from `thread::spawn` to guarantee the +thread is run to completion + +Calling `join` on the handle blocks the thread currently running until the +thread represented by the handle terminates. *Blocking* a thread means that +thread is prevented from performing work or exiting. Because we’ve put the call +to `join` after the main thread’s `for` loop, running Listing 16-2 should +produce output similar to this: + +``` +hi number 1 from the main thread! +hi number 2 from the main thread! +hi number 1 from the spawned thread! +hi number 3 from the main thread! +hi number 2 from the spawned thread! +hi number 4 from the main thread! +hi number 3 from the spawned thread! +hi number 4 from the spawned thread! +hi number 5 from the spawned thread! +hi number 6 from the spawned thread! +hi number 7 from the spawned thread! +hi number 8 from the spawned thread! +hi number 9 from the spawned thread! +``` + +The two threads continue alternating, but the main thread waits because of the +call to `handle.join()` and does not end until the spawned thread is finished. + +But let’s see what happens when we instead move `handle.join()` before the +`for` loop in `main`, like this: + +Filename: src/main.rs + +``` +use std::thread; +use std::time::Duration; + +fn main() { + let handle = thread::spawn(|| { + for i in 1..10 { + println!("hi number {} from the spawned thread!", i); + thread::sleep(Duration::from_millis(1)); + } + }); + + handle.join().unwrap(); + + for i in 1..5 { + println!("hi number {} from the main thread!", i); + thread::sleep(Duration::from_millis(1)); + } +} +``` + +The main thread will wait for the spawned thread to finish and then run its +`for` loop, so the output won’t be interleaved anymore, as shown here: + +``` +hi number 1 from the spawned thread! +hi number 2 from the spawned thread! +hi number 3 from the spawned thread! +hi number 4 from the spawned thread! +hi number 5 from the spawned thread! +hi number 6 from the spawned thread! +hi number 7 from the spawned thread! +hi number 8 from the spawned thread! +hi number 9 from the spawned thread! +hi number 1 from the main thread! +hi number 2 from the main thread! +hi number 3 from the main thread! +hi number 4 from the main thread! +``` + +Small details, such as where `join` is called, can affect whether or not your +threads run at the same time. + +### Using `move` Closures with Threads + +We'll often use the `move` keyword with closures passed to `thread::spawn` +because the closure will then take ownership of the values it uses from the +environment, thus transferring ownership of those values from one thread to +another. In the “Capturing the Environment with Closures” section of Chapter +13, we discussed `move` in the context of closures. Now, we’ll concentrate more +on the interaction between `move` and `thread::spawn`. + +Notice in Listing 16-1 that the closure we pass to `thread::spawn` takes no +arguments: we’re not using any data from the main thread in the spawned +thread’s code. To use data from the main thread in the spawned thread, the +spawned thread’s closure must capture the values it needs. Listing 16-3 shows +an attempt to create a vector in the main thread and use it in the spawned +thread. However, this won’t yet work, as you’ll see in a moment. + +Filename: src/main.rs + +``` +use std::thread; + +fn main() { + let v = vec![1, 2, 3]; + + let handle = thread::spawn(|| { + println!("Here's a vector: {:?}", v); + }); + + handle.join().unwrap(); +} +``` + +Listing 16-3: Attempting to use a vector created by the main thread in another +thread + +The closure uses `v`, so it will capture `v` and make it part of the closure’s +environment. Because `thread::spawn` runs this closure in a new thread, we +should be able to access `v` inside that new thread. But when we compile this +example, we get the following error: + +``` +error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function + --> src/main.rs:6:32 + | +6 | let handle = thread::spawn(|| { + | ^^ may outlive borrowed value `v` +7 | println!("Here's a vector: {:?}", v); + | - `v` is borrowed here + | +note: function requires argument type to outlive `'static` + --> src/main.rs:6:18 + | +6 | let handle = thread::spawn(|| { + | __________________^ +7 | | println!("Here's a vector: {:?}", v); +8 | | }); + | |______^ +help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword + | +6 | let handle = thread::spawn(move || { + | ++++ +``` + +Rust *infers* how to capture `v`, and because `println!` only needs a reference +to `v`, the closure tries to borrow `v`. However, there’s a problem: Rust can’t +tell how long the spawned thread will run, so it doesn’t know if the reference +to `v` will always be valid. + +Listing 16-4 provides a scenario that’s more likely to have a reference to `v` +that won’t be valid: + +Filename: src/main.rs + +``` +use std::thread; + +fn main() { + let v = vec![1, 2, 3]; + + let handle = thread::spawn(|| { + println!("Here's a vector: {:?}", v); + }); + + drop(v); // oh no! + + handle.join().unwrap(); +} +``` + +Listing 16-4: A thread with a closure that attempts to capture a reference to +`v` from a main thread that drops `v` + +If Rust allowed us to run this code, there’s a possibility the spawned thread +would be immediately put in the background without running at all. The spawned +thread has a reference to `v` inside, but the main thread immediately drops +`v`, using the `drop` function we discussed in Chapter 15. Then, when the +spawned thread starts to execute, `v` is no longer valid, so a reference to it +is also invalid. Oh no! + +To fix the compiler error in Listing 16-3, we can use the error message’s +advice: + +``` +help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword + | +6 | let handle = thread::spawn(move || { + | ++++ +``` + +By adding the `move` keyword before the closure, we force the closure to take +ownership of the values it’s using rather than allowing Rust to infer that it +should borrow the values. The modification to Listing 16-3 shown in Listing +16-5 will compile and run as we intend: + +Filename: src/main.rs + +``` +use std::thread; + +fn main() { + let v = vec![1, 2, 3]; + + let handle = thread::spawn(move || { + println!("Here's a vector: {:?}", v); + }); + + handle.join().unwrap(); +} +``` + +Listing 16-5: Using the `move` keyword to force a closure to take ownership of +the values it uses + +We might be tempted to try the same thing to fix the code in Listing 16-4 where +the main thread called `drop` by using a `move` closure. However, this fix will +not work because what Listing 16-4 is trying to do is disallowed for a +different reason. If we added `move` to the closure, we would move `v` into the +closure’s environment, and we could no longer call `drop` on it in the main +thread. We would get this compiler error instead: + +``` +error[E0382]: use of moved value: `v` + --> src/main.rs:10:10 + | +4 | let v = vec![1, 2, 3]; + | - move occurs because `v` has type `Vec<i32>`, which does not implement the `Copy` trait +5 | +6 | let handle = thread::spawn(move || { + | ------- value moved into closure here +7 | println!("Here's a vector: {:?}", v); + | - variable moved due to use in closure +... +10 | drop(v); // oh no! + | ^ value used here after move +``` + +Rust’s ownership rules have saved us again! We got an error from the code in +Listing 16-3 because Rust was being conservative and only borrowing `v` for the +thread, which meant the main thread could theoretically invalidate the spawned +thread’s reference. By telling Rust to move ownership of `v` to the spawned +thread, we’re guaranteeing Rust that the main thread won’t use `v` anymore. If +we change Listing 16-4 in the same way, we’re then violating the ownership +rules when we try to use `v` in the main thread. The `move` keyword overrides +Rust’s conservative default of borrowing; it doesn’t let us violate the +ownership rules. + +With a basic understanding of threads and the thread API, let’s look at what we +can *do* with threads. + +## Using Message Passing to Transfer Data Between Threads + +One increasingly popular approach to ensuring safe concurrency is *message +passing*, where threads or actors communicate by sending each other messages +containing data. Here’s the idea in a slogan from the Go language +documentation at *https://golang.org/doc/effective_go.html#concurrency*: +“Do not communicate by sharing memory; instead, share memory by communicating.” + +<!-- are they communicating to decide which thread should be running, or by +"communicate" do we just mean sharing data? /LC --> +<!-- Just sharing data. Is there something that should be clarified here? I'm +not sure what to do because this paragraph doesn't mention deciding which +thread should be running, it only mentions sharing data, so I'm not sure where +the possible confusion is coming from. /Carol --> +<!-- JT, if this will be already obvious to a reader, no changes needed. I just +wanted to ensure there was no potential confusion around what is being +communicated /LC --> +<!-- I like that we want to give a shout-out to Go's thinking process when +we align, though I made a bit of a face reading the quote. "Share memory" is a +such a loaded concept that I think people might stumble a bit over the play on +the technical words. + +Funnily the next line following that quote in the Go book is: + +"This approach can be taken too far." :D +/JT --> +<!-- I think this means JT is fine leaving this the way it is! /Carol --> + +To accomplish message-sending concurrency, Rust's standard library provides an +implementation of *channels*. A channel is a general programming concept by +which data is sent from one thread to another. + +You can imagine a channel in programming as being like a directional channel of +water, such as a stream or a river. If you put something like a rubber duck +into a river, it will travel downstream to the end of the waterway. + +A channel has two halves: a transmitter and a receiver. The transmitter half is +the upstream location where you put rubber ducks into the river, and the +receiver half is where the rubber duck ends up downstream. One part of your +code calls methods on the transmitter with the data you want to send, and +another part checks the receiving end for arriving messages. A channel is said +to be *closed* if either the transmitter or receiver half is dropped. + +Here, we’ll work up to a program that has one thread to generate values and +send them down a channel, and another thread that will receive the values and +print them out. We’ll be sending simple values between threads using a channel +to illustrate the feature. Once you’re familiar with the technique, you could +use channels for any threads that needs to communicate between each other, such +as a chat system or a system where many threads perform parts of a calculation +and send the parts to one thread that aggregates the results. + +First, in Listing 16-6, we’ll create a channel but not do anything with it. +Note that this won’t compile yet because Rust can’t tell what type of values we +want to send over the channel. + +Filename: src/main.rs + +``` +use std::sync::mpsc; + +fn main() { + let (tx, rx) = mpsc::channel(); +} +``` + +Listing 16-6: Creating a channel and assigning the two halves to `tx` and `rx` + +We create a new channel using the `mpsc::channel` function; `mpsc` stands for +*multiple producer, single consumer*. In short, the way Rust’s standard library +implements channels means a channel can have multiple *sending* ends that +produce values but only one *receiving* end that consumes those values. Imagine +multiple streams flowing together into one big river: everything sent down any +of the streams will end up in one river at the end. We’ll start with a single +producer for now, but we’ll add multiple producers when we get this example +working. + +The `mpsc::channel` function returns a tuple, the first element of which is the +sending end--the transmitter--and the second element is the receiving end--the +receiver. The abbreviations `tx` and `rx` are traditionally used in many fields +for *transmitter* and *receiver* respectively, so we name our variables as such +to indicate each end. We’re using a `let` statement with a pattern that +destructures the tuples; we’ll discuss the use of patterns in `let` statements +and destructuring in Chapter 18. For now, know that using a `let` statement +this way is a convenient approach to extract the pieces of the tuple returned +by `mpsc::channel`. + +Let’s move the transmitting end into a spawned thread and have it send one +string so the spawned thread is communicating with the main thread, as shown in +Listing 16-7. This is like putting a rubber duck in the river upstream or +sending a chat message from one thread to another. + +Filename: src/main.rs + +``` +use std::sync::mpsc; +use std::thread; + +fn main() { + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let val = String::from("hi"); + tx.send(val).unwrap(); + }); +} +``` + +Listing 16-7: Moving `tx` to a spawned thread and sending “hi” + +Again, we’re using `thread::spawn` to create a new thread and then using `move` +to move `tx` into the closure so the spawned thread owns `tx`. The spawned +thread needs to own the transmitter to be able to send messages through the +channel. + +The transmitter has a `send` method that takes the value we want to send. +The `send` method returns a `Result<T, E>` type, so if the receiver has +already been dropped and there’s nowhere to send a value, the send operation +will return an error. In this example, we’re calling `unwrap` to panic in case +of an error. But in a real application, we would handle it properly: return to +Chapter 9 to review strategies for proper error handling. + +In Listing 16-8, we’ll get the value from the receiver in the main thread. This +is like retrieving the rubber duck from the water at the end of the river or +receiving a chat message. + +Filename: src/main.rs + +``` +use std::sync::mpsc; +use std::thread; + +fn main() { + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let val = String::from("hi"); + tx.send(val).unwrap(); + }); + + let received = rx.recv().unwrap(); + println!("Got: {}", received); +} +``` + +Listing 16-8: Receiving the value “hi” in the main thread and printing it + +The receiver has two useful methods: `recv` and `try_recv`. We’re using `recv`, +short for *receive*, which will block the main thread’s execution and wait +until a value is sent down the channel. Once a value is sent, `recv` will +return it in a `Result<T, E>`. When the transmitter closes, `recv` will return +an error to signal that no more values will be coming. + +The `try_recv` method doesn’t block, but will instead return a `Result<T, E>` +immediately: an `Ok` value holding a message if one is available and an `Err` +value if there aren’t any messages this time. Using `try_recv` is useful if +this thread has other work to do while waiting for messages: we could write a +loop that calls `try_recv` every so often, handles a message if one is +available, and otherwise does other work for a little while until checking +again. + +We’ve used `recv` in this example for simplicity; we don’t have any other work +for the main thread to do other than wait for messages, so blocking the main +thread is appropriate. + +When we run the code in Listing 16-8, we’ll see the value printed from the main +thread: + +``` +Got: hi +``` + +Perfect! + +### Channels and Ownership Transference + +The ownership rules play a vital role in message sending because they help you +write safe, concurrent code. Preventing errors in concurrent programming is the +advantage of thinking about ownership throughout your Rust programs. Let’s do +an experiment to show how channels and ownership work together to prevent +problems: we’ll try to use a `val` value in the spawned thread *after* we’ve +sent it down the channel. Try compiling the code in Listing 16-9 to see why +this code isn’t allowed: + +Filename: src/main.rs + +``` +use std::sync::mpsc; +use std::thread; + +fn main() { + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let val = String::from("hi"); + tx.send(val).unwrap(); + println!("val is {}", val); + }); + + let received = rx.recv().unwrap(); + println!("Got: {}", received); +} +``` + +Listing 16-9: Attempting to use `val` after we’ve sent it down the channel + +Here, we try to print `val` after we’ve sent it down the channel via `tx.send`. +Allowing this would be a bad idea: once the value has been sent to another +thread, that thread could modify or drop it before we try to use the value +again. Potentially, the other thread’s modifications could cause errors or +unexpected results due to inconsistent or nonexistent data. However, Rust gives +us an error if we try to compile the code in Listing 16-9: + +``` +error[E0382]: borrow of moved value: `val` + --> src/main.rs:10:31 + | +8 | let val = String::from("hi"); + | --- move occurs because `val` has type `String`, which does not implement the `Copy` trait +9 | tx.send(val).unwrap(); + | --- value moved here +10 | println!("val is {}", val); + | ^^^ value borrowed here after move +``` + +Our concurrency mistake has caused a compile time error. The `send` function +takes ownership of its parameter, and when the value is moved, the receiver +takes ownership of it. This stops us from accidentally using the value again +after sending it; the ownership system checks that everything is okay. + +### Sending Multiple Values and Seeing the Receiver Waiting + +The code in Listing 16-8 compiled and ran, but it didn’t clearly show us that +two separate threads were talking to each other over the channel. In Listing +16-10 we’ve made some modifications that will prove the code in Listing 16-8 is +running concurrently: the spawned thread will now send multiple messages and +pause for a second between each message. + +Filename: src/main.rs + +``` +use std::sync::mpsc; +use std::thread; +use std::time::Duration; + +fn main() { + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let vals = vec![ + String::from("hi"), + String::from("from"), + String::from("the"), + String::from("thread"), + ]; + + for val in vals { + tx.send(val).unwrap(); + thread::sleep(Duration::from_secs(1)); + } + }); + + for received in rx { + println!("Got: {}", received); + } +} +``` + +Listing 16-10: Sending multiple messages and pausing between each + +This time, the spawned thread has a vector of strings that we want to send to +the main thread. We iterate over them, sending each individually, and pause +between each by calling the `thread::sleep` function with a `Duration` value of +1 second. + +In the main thread, we’re not calling the `recv` function explicitly anymore: +instead, we’re treating `rx` as an iterator. For each value received, we’re +printing it. When the channel is closed, iteration will end. + +When running the code in Listing 16-10, you should see the following output +with a 1-second pause in between each line: + +``` +Got: hi +Got: from +Got: the +Got: thread +``` + +Because we don’t have any code that pauses or delays in the `for` loop in the +main thread, we can tell that the main thread is waiting to receive values from +the spawned thread. + +### Creating Multiple Producers by Cloning the Transmitter + +Earlier we mentioned that `mpsc` was an acronym for *multiple producer, +single consumer*. Let’s put `mpsc` to use and expand the code in Listing 16-10 +to create multiple threads that all send values to the same receiver. We can do +so by cloning the transmitter, as shown in Listing 16-11: + +Filename: src/main.rs + +``` + // --snip-- + + let (tx, rx) = mpsc::channel(); + + let tx1 = tx.clone(); + thread::spawn(move || { + let vals = vec![ + String::from("hi"), + String::from("from"), + String::from("the"), + String::from("thread"), + ]; + + for val in vals { + tx1.send(val).unwrap(); + thread::sleep(Duration::from_secs(1)); + } + }); + + thread::spawn(move || { + let vals = vec![ + String::from("more"), + String::from("messages"), + String::from("for"), + String::from("you"), + ]; + + for val in vals { + tx.send(val).unwrap(); + thread::sleep(Duration::from_secs(1)); + } + }); + + for received in rx { + println!("Got: {}", received); + } + + // --snip-- +``` + +Listing 16-11: Sending multiple messages from multiple producers + +This time, before we create the first spawned thread, we call `clone` on the +transmitter. This will give us a new transmitter we can pass to the first +spawned thread. We pass the original transmitter to a second spawned thread. +This gives us two threads, each sending different messages to the one receiver. + +When you run the code, your output should look something like this: + +``` +Got: hi +Got: more +Got: from +Got: messages +Got: for +Got: the +Got: thread +Got: you +``` + +You might see the values in another order, depending on your system. This is +what makes concurrency interesting as well as difficult. If you experiment with +`thread::sleep`, giving it various values in the different threads, each run +will be more nondeterministic and create different output each time. + +Now that we’ve looked at how channels work, let’s look at a different method of +concurrency. + +## Shared-State Concurrency + +Message passing is a fine way of handling concurrency, but it’s not the only +one. Another method would be for multiple threads to access the same shared +data. Consider this part of the slogan from the Go language documentation +again: “do not communicate by sharing memory.” + +<!-- NB: if we decide to do anything with the Go quote above, we also +reference it here. +/JT --> +<!-- Also not changing anything here. /Carol --> + +What would communicating by sharing memory look like? In addition, why would +message-passing enthusiasts caution not to use memory sharing? + +In a way, channels in any programming language are similar to single ownership, +because once you transfer a value down a channel, you should no longer use that +value. Shared memory concurrency is like multiple ownership: multiple threads +can access the same memory location at the same time. As you saw in Chapter 15, +where smart pointers made multiple ownership possible, multiple ownership can +add complexity because these different owners need managing. Rust’s type system +and ownership rules greatly assist in getting this management correct. For an +example, let’s look at mutexes, one of the more common concurrency primitives +for shared memory. + +### Using Mutexes to Allow Access to Data from One Thread at a Time + +*Mutex* is an abbreviation for *mutual exclusion*, as in, a mutex allows only +one thread to access some data at any given time. To access the data in a +mutex, a thread must first signal that it wants access by asking to acquire the +mutex’s *lock*. The lock is a data structure that is part of the mutex that +keeps track of who currently has exclusive access to the data. Therefore, the +mutex is described as *guarding* the data it holds via the locking system. + +Mutexes have a reputation for being difficult to use because you have to +remember two rules: + +* You must attempt to acquire the lock before using the data. +* When you’re done with the data that the mutex guards, you must unlock the + data so other threads can acquire the lock. + +For a real-world metaphor for a mutex, imagine a panel discussion at a +conference with only one microphone. Before a panelist can speak, they have to +ask or signal that they want to use the microphone. When they get the +microphone, they can talk for as long as they want to and then hand the +microphone to the next panelist who requests to speak. If a panelist forgets to +hand the microphone off when they’re finished with it, no one else is able to +speak. If management of the shared microphone goes wrong, the panel won’t work +as planned! + +Management of mutexes can be incredibly tricky to get right, which is why so +many people are enthusiastic about channels. However, thanks to Rust’s type +system and ownership rules, you can’t get locking and unlocking wrong. + +#### The API of `Mutex<T>` + +As an example of how to use a mutex, let’s start by using a mutex in a +single-threaded context, as shown in Listing 16-12: + +Filename: src/main.rs + +``` +use std::sync::Mutex; + +fn main() { + [1] let m = Mutex::new(5); + + { + [2] let mut num = m.lock().unwrap(); + [3] *num = 6; + [4] } + + [5] println!("m = {:?}", m); +} +``` + +Listing 16-12: Exploring the API of `Mutex<T>` in a single-threaded context for +simplicity + +As with many types, we create a `Mutex<T>` using the associated function `new` +[1]. To access the data inside the mutex, we use the `lock` method to acquire +the lock [2]. This call will block the current thread so it can’t do any work +until it’s our turn to have the lock. + +The call to `lock` would fail if another thread holding the lock panicked. In +that case, no one would ever be able to get the lock, so we’ve chosen to +`unwrap` and have this thread panic if we’re in that situation. + +After we’ve acquired the lock, we can treat the return value, named `num` in +this case, as a mutable reference to the data inside. The type system ensures +that we acquire a lock before using the value in `m`. The type of `m` is +`Mutex<i32>`, not `i32`, so we *must* call `lock` to be able to use the `i32` +value. We can’t forget; the type system won’t let us access the inner `i32` +otherwise. + +As you might suspect, `Mutex<T>` is a smart pointer. More accurately, the call +to `lock` *returns* a smart pointer called `MutexGuard`, wrapped in a +`LockResult` that we handled with the call to `unwrap`. The `MutexGuard` smart +pointer implements `Deref` to point at our inner data; the smart pointer also +has a `Drop` implementation that releases the lock automatically when a +`MutexGuard` goes out of scope, which happens at the end of the inner scope +[4]. As a result, we don’t risk forgetting to release the lock and blocking the +mutex from being used by other threads, because the lock release happens +automatically. + +After dropping the lock, we can print the mutex value and see that we were able +to change the inner `i32` to 6 [5]. + +#### Sharing a `Mutex<T>` Between Multiple Threads + +Now, let’s try to share a value between multiple threads using `Mutex<T>`. +We’ll spin up 10 threads and have them each increment a counter value by 1, so +the counter goes from 0 to 10. The next example in Listing 16-13 will have +a compiler error, and we’ll use that error to learn more about using +`Mutex<T>` and how Rust helps us use it correctly. + +Filename: src/main.rs + +``` +use std::sync::Mutex; +use std::thread; + +fn main() { + [1] let counter = Mutex::new(0); + let mut handles = vec![]; + + [2] for _ in 0..10 { + [3] let handle = thread::spawn(move || { + [4] let mut num = counter.lock().unwrap(); + + [5] *num += 1; + }); + [6] handles.push(handle); + } + + for handle in handles { + [7] handle.join().unwrap(); + } + + [8] println!("Result: {}", *counter.lock().unwrap()); +} +``` + +Listing 16-13: Ten threads each increment a counter guarded by a `Mutex<T>` + +We create a `counter` variable to hold an `i32` inside a `Mutex<T>` [1], as we +did in Listing 16-12. Next, we create 10 threads by iterating over a range of +numbers [2]. We use `thread::spawn` and give all the threads the same closure: +one that moves the counter into the thread [3], acquires a lock on the +`Mutex<T>` by calling the `lock` method [4], and then adds 1 to the value in +the mutex [5]. When a thread finishes running its closure, `num` will go out of +scope and release the lock so another thread can acquire it. + +In the main thread, we collect all the join handles [6]. Then, as we did in +Listing 16-2, we call `join` on each handle to make sure all the threads finish +[7]. At that point, the main thread will acquire the lock and print the result +of this program [8]. + +We hinted that this example wouldn’t compile. Now let’s find out why! + +``` +error[E0382]: use of moved value: `counter` + --> src/main.rs:9:36 + | +5 | let counter = Mutex::new(0); + | ------- move occurs because `counter` has type `Mutex<i32>`, which does not implement the `Copy` trait +... +9 | let handle = thread::spawn(move || { + | ^^^^^^^ value moved into closure here, in previous iteration of loop +10 | let mut num = counter.lock().unwrap(); + | ------- use occurs due to use in closure +``` + +The error message states that the `counter` value was moved in the previous +iteration of the loop. Rust is telling us that we can’t move the ownership +of lock `counter` into multiple threads. Let’s fix the compiler error with a +multiple-ownership method we discussed in Chapter 15. + +#### Multiple Ownership with Multiple Threads + +In Chapter 15, we gave a value multiple owners by using the smart pointer +`Rc<T>` to create a reference counted value. Let’s do the same here and see +what happens. We’ll wrap the `Mutex<T>` in `Rc<T>` in Listing 16-14 and clone +the `Rc<T>` before moving ownership to the thread. + +Filename: src/main.rs + +``` +use std::rc::Rc; +use std::sync::Mutex; +use std::thread; + +fn main() { + let counter = Rc::new(Mutex::new(0)); + let mut handles = vec![]; + + for _ in 0..10 { + let counter = Rc::clone(&counter); + let handle = thread::spawn(move || { + let mut num = counter.lock().unwrap(); + + *num += 1; + }); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + println!("Result: {}", *counter.lock().unwrap()); +} +``` + +Listing 16-14: Attempting to use `Rc<T>` to allow multiple threads to own the +`Mutex<T>` + +Once again, we compile and get... different errors! The compiler is teaching us +a lot. + +``` +[1] error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely + --> src/main.rs:11:22 + | +11 | let handle = thread::spawn(move || { + | ______________________^^^^^^^^^^^^^_- + | | | + | | `Rc<Mutex<i32>>` cannot be sent between threads safely +12 | | let mut num = counter.lock().unwrap(); +13 | | +14 | | *num += 1; +15 | | }); + | |_________- within this `[closure@src/main.rs:11:36: 15:10]` + | +[2] = help: within `[closure@src/main.rs:11:36: 15:10]`, the trait `Send` is not implemented for `Rc<Mutex<i32>>` + = note: required because it appears within the type `[closure@src/main.rs:11:36: 15:10]` +note: required by a bound in `spawn` +``` + +Wow, that error message is very wordy! Here’s the important part to focus on: +`` `Rc<Mutex<i32>>` cannot be sent between threads safely `` [1]. The compiler +is also telling us the reason why: ``the trait `Send` is not implemented for +`Rc<Mutex<i32>>` `` [2]. We’ll talk about `Send` in the next section: it’s one +of the traits that ensures the types we use with threads are meant for use in +concurrent situations. + +Unfortunately, `Rc<T>` is not safe to share across threads. When `Rc<T>` +manages the reference count, it adds to the count for each call to `clone` and +subtracts from the count when each clone is dropped. But it doesn’t use any +concurrency primitives to make sure that changes to the count can’t be +interrupted by another thread. This could lead to wrong counts—subtle bugs that +could in turn lead to memory leaks or a value being dropped before we’re done +with it. What we need is a type exactly like `Rc<T>` but one that makes changes +to the reference count in a thread-safe way. + +#### Atomic Reference Counting with `Arc<T>` + +Fortunately, `Arc<T>` *is* a type like `Rc<T>` that is safe to use in +concurrent situations. The *a* stands for *atomic*, meaning it’s an *atomically +reference counted* type. Atomics are an additional kind of concurrency +primitive that we won’t cover in detail here: see the standard library +documentation for `std::sync::atomic` for more details. At this point, you just +need to know that atomics work like primitive types but are safe to share +across threads. + +You might then wonder why all primitive types aren’t atomic and why standard +library types aren’t implemented to use `Arc<T>` by default. The reason is that +thread safety comes with a performance penalty that you only want to pay when +you really need to. If you’re just performing operations on values within a +single thread, your code can run faster if it doesn’t have to enforce the +guarantees atomics provide. + +Let’s return to our example: `Arc<T>` and `Rc<T>` have the same API, so we fix +our program by changing the `use` line, the call to `new`, and the call to +`clone`. The code in Listing 16-15 will finally compile and run: + +Filename: src/main.rs + +``` +use std::sync::{Arc, Mutex}; +use std::thread; + +fn main() { + let counter = Arc::new(Mutex::new(0)); + let mut handles = vec![]; + + for _ in 0..10 { + let counter = Arc::clone(&counter); + let handle = thread::spawn(move || { + let mut num = counter.lock().unwrap(); + + *num += 1; + }); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + println!("Result: {}", *counter.lock().unwrap()); +} +``` + +Listing 16-15: Using an `Arc<T>` to wrap the `Mutex<T>` to be able to share +ownership across multiple threads + +This code will print the following: + +``` +Result: 10 +``` + +We did it! We counted from 0 to 10, which may not seem very impressive, but it +did teach us a lot about `Mutex<T>` and thread safety. You could also use this +program’s structure to do more complicated operations than just incrementing a +counter. Using this strategy, you can divide a calculation into independent +parts, split those parts across threads, and then use a `Mutex<T>` to have each +thread update the final result with its part. + +Note that if you are doing simple numerical operations, there are types simpler +than `Mutex<T>` types provided by the `std::sync::atomic` module of the +standard library. These types provide safe, concurrent, atomic access to +primitive types. We chose to use `Mutex<T>` with a primitive type for this +example so we could concentrate on how `Mutex<T>` works. + +<!-- Do we want to mention that for simple counters we have simpler types in +the standard library? (eg, AtomicI64 for the above) +/JT --> +<!-- Done! /Carol--> + +### Similarities Between `RefCell<T>`/`Rc<T>` and `Mutex<T>`/`Arc<T>` + +You might have noticed that `counter` is immutable but we could get a mutable +reference to the value inside it; this means `Mutex<T>` provides interior +mutability, as the `Cell` family does. In the same way we used `RefCell<T>` in +Chapter 15 to allow us to mutate contents inside an `Rc<T>`, we use `Mutex<T>` +to mutate contents inside an `Arc<T>`. + +Another detail to note is that Rust can’t protect you from all kinds of logic +errors when you use `Mutex<T>`. Recall in Chapter 15 that using `Rc<T>` came +with the risk of creating reference cycles, where two `Rc<T>` values refer to +each other, causing memory leaks. Similarly, `Mutex<T>` comes with the risk of +creating *deadlocks*. These occur when an operation needs to lock two resources +and two threads have each acquired one of the locks, causing them to wait for +each other forever. If you’re interested in deadlocks, try creating a Rust +program that has a deadlock; then research deadlock mitigation strategies for +mutexes in any language and have a go at implementing them in Rust. The +standard library API documentation for `Mutex<T>` and `MutexGuard` offers +useful information. + +We’ll round out this chapter by talking about the `Send` and `Sync` traits and +how we can use them with custom types. + +## Extensible Concurrency with the `Sync` and `Send` Traits + +Interestingly, the Rust language has *very* few concurrency features. Almost +every concurrency feature we’ve talked about so far in this chapter has been +part of the standard library, not the language. Your options for handling +concurrency are not limited to the language or the standard library; you can +write your own concurrency features or use those written by others. + +However, two concurrency concepts are embedded in the language: the +`std::marker` traits `Sync` and `Send`. + +### Allowing Transference of Ownership Between Threads with `Send` + +The `Send` marker trait indicates that ownership of values of the type +implementing `Send` can be transferred between threads. Almost every Rust type +is `Send`, but there are some exceptions, including `Rc<T>`: this cannot be +`Send` because if you cloned an `Rc<T>` value and tried to transfer ownership +of the clone to another thread, both threads might update the reference count +at the same time. For this reason, `Rc<T>` is implemented for use in +single-threaded situations where you don’t want to pay the thread-safe +performance penalty. + +Therefore, Rust’s type system and trait bounds ensure that you can never +accidentally send an `Rc<T>` value across threads unsafely. When we tried to do +this in Listing 16-14, we got the error `the trait Send is not implemented for +Rc<Mutex<i32>>`. When we switched to `Arc<T>`, which is `Send`, the code +compiled. + +Any type composed entirely of `Send` types is automatically marked as `Send` as +well. Almost all primitive types are `Send`, aside from raw pointers, which +we’ll discuss in Chapter 19. + +### Allowing Access from Multiple Threads with `Sync` + +The `Sync` marker trait indicates that it is safe for the type implementing +`Sync` to be referenced from multiple threads. In other words, any type `T` is +`Sync` if `&T` (an immutable reference to `T`) is `Send`, meaning the reference +can be sent safely to another thread. Similar to `Send`, primitive types are +`Sync`, and types composed entirely of types that are `Sync` are also `Sync`. + +The smart pointer `Rc<T>` is also not `Sync` for the same reasons that it’s not +`Send`. The `RefCell<T>` type (which we talked about in Chapter 15) and the +family of related `Cell<T>` types are not `Sync`. The implementation of borrow +checking that `RefCell<T>` does at runtime is not thread-safe. The smart +pointer `Mutex<T>` is `Sync` and can be used to share access with multiple +threads as you saw in the “Sharing a `Mutex<T>` Between Multiple +Threads” section. + +### Implementing `Send` and `Sync` Manually Is Unsafe + +Because types that are made up of `Send` and `Sync` traits are automatically +also `Send` and `Sync`, we don’t have to implement those traits manually. As +marker traits, they don’t even have any methods to implement. They’re just +useful for enforcing invariants related to concurrency. + +Manually implementing these traits involves implementing unsafe Rust code. +We’ll talk about using unsafe Rust code in Chapter 19; for now, the important +information is that building new concurrent types not made up of `Send` and +`Sync` parts requires careful thought to uphold the safety guarantees. “The +Rustonomicon” at *https://doc.rust-lang.org/stable/nomicon/* has more +information about these guarantees and how to uphold them. + +## Summary + +This isn’t the last you’ll see of concurrency in this book: the project in +Chapter 20 will use the concepts in this chapter in a more realistic situation +than the smaller examples discussed here. + +As mentioned earlier, because very little of how Rust handles concurrency is +part of the language, many concurrency solutions are implemented as crates. +These evolve more quickly than the standard library, so be sure to search +online for the current, state-of-the-art crates to use in multithreaded +situations. + +The Rust standard library provides channels for message passing and smart +pointer types, such as `Mutex<T>` and `Arc<T>`, that are safe to use in +concurrent contexts. The type system and the borrow checker ensure that the +code using these solutions won’t end up with data races or invalid references. +Once you get your code to compile, you can rest assured that it will happily +run on multiple threads without the kinds of hard-to-track-down bugs common in +other languages. Concurrent programming is no longer a concept to be afraid of: +go forth and make your programs concurrent, fearlessly! + +Next, we’ll talk about idiomatic ways to model problems and structure solutions +as your Rust programs get bigger. In addition, we’ll discuss how Rust’s idioms +relate to those you might be familiar with from object-oriented programming. |