diff --git a/second-edition/nostarch/chapter16.md b/second-edition/nostarch/chapter16.md index a1c18f896..fa1745bf2 100644 --- a/second-edition/nostarch/chapter16.md +++ b/second-edition/nostarch/chapter16.md @@ -25,24 +25,12 @@ shipped to production. We’ve nicknamed this aspect of Rust *fearless concurrency*. Fearless concurrency allows you to write code that’s free of subtle bugs and is easy to refactor without introducing new bugs. - - - > Note: we’ll be referring to many of the problems here as *concurrent* rather > than being more precise by saying *concurrent and/or parallel*, for > simplicity’s sake. If this were a book specifically about concurrency and/or > parallelism, we’d be sure to be more specific. For this chapter, please > mentally substitute *concurrent and/or parallel* whenever we say *concurrent*. - - - Many languages are strongly opinionated about the solutions they offer for dealing with concurrent problems. For example, Erlang has elegant functionality for message passing concurrency, but only obscure ways to share state between @@ -71,10 +59,6 @@ In most operating systems today, an executed program’s code is run in a your program, you can also have independent parts that run simultaneously. The feature that runs these independent parts is called *threads*. - - - Splitting the computation in your program up into multiple threads can improve performance, since the program will be doing multiple things at the same time, but it also adds complexity. Because threads may run simultaneously, there’s no @@ -88,10 +72,6 @@ threads will run. This can lead to problems such as: - Bugs that only happen in certain situations and are hard to reproduce and fix reliably - - - Rust attempts to mitigate negative effects of using threads. Programming in a multithreaded context still takes careful thought and requires a code structure that’s different from programs that run in a single thread. @@ -112,13 +92,6 @@ Each model has its own advantages and tradeoffs, and the tradeoff most important to Rust is runtime support. *Runtime* is a confusing term and can have different meanings in different contexts. - - - In this context, by runtime we mean code that’s included by the language in every binary. This code can be large or small depending on the language, but every non-assembly language will have some amount of runtime code. For that @@ -185,10 +158,6 @@ hi number 4 from the spawned thread! hi number 5 from the spawned thread! ``` - - - The threads will probably take turns, but that’s not 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 @@ -206,10 +175,6 @@ the time, because the main thread ends before the spawned thread is done, there’s actually no guarantee that the spawned thread will get to run at all, because there’s no guarantee on the order in which threads run! - - - We can fix this 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 @@ -217,11 +182,6 @@ finish. Listing 16-2 shows how to use the `JoinHandle` of the thread we created in Listing 16-1 and call `join` in order to make sure the spawned thread finishes before the `main` exits: - - - Filename: src/main.rs ``` @@ -251,10 +211,6 @@ thread is prevented from performing work or exiting. Because we’ve put the cal to `join` after the main thread’s `for` loop, running this example should produce output that looks something like this: - - ``` hi number 1 from the main thread! hi number 2 from the main thread! @@ -327,8 +283,6 @@ another thread. In Chapter 13, we said that “Creating closures that capture values from their environment is mostly used in the context of starting new threads.” - - Now we’re creating new threads, so let’s talk about capturing values in closures! @@ -448,9 +402,6 @@ fn main() { Listing 16-5: Using the `move` keyword to force a closure to take ownership of the values it uses - - - What would happen to the code in Listing 16-4 where the main thread called `drop` if we use a `move` closure? Would `move` fix that case? Nope, we get a different error, because what Listing 16-4 is trying to do isn’t allowed for a @@ -482,11 +433,6 @@ 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. - - - Now that we have a basic understanding of threads and the thread API, let’s talk about what we can actually *do* with threads. @@ -502,9 +448,6 @@ documentation: > > --Effective Go at *http://golang.org/doc/effective_go.html* - - - One major tool Rust has for accomplishing message sending concurrency is the *channel*, a programming concept that Rust’s standard library provides an implementation of. You can imagine a channel in programming like a channel of @@ -557,21 +500,11 @@ discussing the use of patterns in `let` statements and destructuring in Chapter 18. Using a `let` statement in this way is a convenient way 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 that the spawned thread is communicating with the main thread, 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 ``` @@ -634,11 +567,6 @@ sent, `recv` will return it in a `Result`. When the sending end of the channel 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` 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 @@ -651,10 +579,6 @@ We’ve chosen to use `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. - - - If we run the code in Listing 16-8, we’ll see the value printed out from the main thread: @@ -666,18 +590,6 @@ Perfect! ### Channels and Ownership Transference - - - The ownership rules play a vital role in message sending as far as helping us write safe, concurrent code. Preventing errors in concurrent programming is the advantage we get by making the tradeoff of having to think about ownership @@ -797,15 +709,6 @@ 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 Near the start of this section, we mentioned that `mpsc` stood for *multiple @@ -891,12 +794,6 @@ only one. Consider this slogan again: What would “communicate by sharing memory” look like? And moreover, why would message passing enthusiasts choose not to use it and do the opposite instead? - - - In a way, channels in any programming language are sort of like single ownership, because once you transfer a value down a channel, you shouldn’t use that value any longer. Shared memory concurrency is sort of like multiple @@ -922,10 +819,6 @@ data it holds via the locking system. Mutexes have a reputation for being hard to use because there are some rules you have to remember: - - - 1. You must attempt to acquire the lock before using the data. 2. Once you’re done with the data that’s guarded by the mutex, you must unlock the data so other threads can acquire the lock. @@ -973,18 +866,10 @@ To access the data inside the mutex, we use the `lock` method to acquire the lock. This call will block the current thread so that 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. - - - Once 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 this value: `Mutex` is not an `i32`, @@ -1055,11 +940,6 @@ program. We hinted that this example won’t compile, now let’s find out why! - - - ``` error[E0382]: capture of moved value: `counter` --> @@ -1220,11 +1100,6 @@ Send is not satisfied`. We’re going to 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` is not safe to share across threads. When `Rc` 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 @@ -1319,17 +1194,6 @@ in any language, and have a go at implementing them in Rust. The standard library API documentation for `Mutex` and `MutexGuard` will have useful information. - - - - Let’s round out this chapter by talking about the `Send` and `Sync` traits and how we could use them with custom types. diff --git a/second-edition/src/ch16-00-concurrency.md b/second-edition/src/ch16-00-concurrency.md index e06e6bff1..fdce98dfa 100644 --- a/second-edition/src/ch16-00-concurrency.md +++ b/second-edition/src/ch16-00-concurrency.md @@ -22,24 +22,12 @@ shipped to production. We’ve nicknamed this aspect of Rust *fearless concurrency*. Fearless concurrency allows you to write code that’s free of subtle bugs and is easy to refactor without introducing new bugs. - - - > Note: we’ll be referring to many of the problems here as *concurrent* rather > than being more precise by saying *concurrent and/or parallel*, for > simplicity’s sake. If this were a book specifically about concurrency and/or > parallelism, we’d be sure to be more specific. For this chapter, please > mentally substitute *concurrent and/or parallel* whenever we say *concurrent*. - - - Many languages are strongly opinionated about the solutions they offer for dealing with concurrent problems. For example, Erlang has elegant functionality for message passing concurrency, but only obscure ways to share state between diff --git a/second-edition/src/ch16-01-threads.md b/second-edition/src/ch16-01-threads.md index 0230c68ff..0ddd57564 100644 --- a/second-edition/src/ch16-01-threads.md +++ b/second-edition/src/ch16-01-threads.md @@ -5,10 +5,6 @@ In most operating systems today, an executed program’s code is run in a your program, you can also have independent parts that run simultaneously. The feature that runs these independent parts is called *threads*. - - - Splitting the computation in your program up into multiple threads can improve performance, since the program will be doing multiple things at the same time, but it also adds complexity. Because threads may run simultaneously, there’s no @@ -22,10 +18,6 @@ threads will run. This can lead to problems such as: - Bugs that only happen in certain situations and are hard to reproduce and fix reliably - - - Rust attempts to mitigate negative effects of using threads. Programming in a multithreaded context still takes careful thought and requires a code structure that’s different from programs that run in a single thread. @@ -46,13 +38,6 @@ Each model has its own advantages and tradeoffs, and the tradeoff most important to Rust is runtime support. *Runtime* is a confusing term and can have different meanings in different contexts. - - - In this context, by runtime we mean code that’s included by the language in every binary. This code can be large or small depending on the language, but every non-assembly language will have some amount of runtime code. For that @@ -122,16 +107,12 @@ hi number 4 from the spawned thread! hi number 5 from the spawned thread! ``` - - - -The `thread::sleep` function will force a thread to stop its execution for a +The `thread::sleep` function will force a thread to stop its execution for a short duration, allowing a different thread to run, so that they will probably -take turns, but that’s not guaranteed: it depends on how your operating system -schedules the threads. In this run, the main thread printed first, even though +take turns, but that’s not 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 +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 one thread, or don’t see any overlap, try @@ -145,10 +126,6 @@ the time, because the main thread ends before the spawned thread is done, there’s actually no guarantee that the spawned thread will get to run at all, because there’s no guarantee on the order in which threads run! - - - We can fix this 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 @@ -156,11 +133,6 @@ finish. Listing 16-2 shows how to use the `JoinHandle` of the thread we created in Listing 16-1 and call `join` in order to make sure the spawned thread finishes before the `main` exits: - - - Filename: src/main.rs ```rust @@ -193,10 +165,6 @@ thread is prevented from performing work or exiting. Because we’ve put the cal to `join` after the main thread’s `for` loop, running this example should produce output that looks something like this: - - ```text hi number 1 from the main thread! hi number 2 from the main thread! @@ -393,9 +361,6 @@ fn main() { Listing 16-5: Using the `move` keyword to force a closure to take ownership of the values it uses - - - What would happen to the code in Listing 16-4 where the main thread called `drop` if we use a `move` closure? Would `move` fix that case? Nope, we get a different error, because what Listing 16-4 is trying to do isn’t allowed for a @@ -427,10 +392,5 @@ 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. - - - Now that we have a basic understanding of threads and the thread API, let’s talk about what we can actually *do* with threads. diff --git a/second-edition/src/ch16-02-message-passing.md b/second-edition/src/ch16-02-message-passing.md index f13bf26a7..9e63ccae9 100644 --- a/second-edition/src/ch16-02-message-passing.md +++ b/second-edition/src/ch16-02-message-passing.md @@ -10,9 +10,6 @@ documentation: > > --[Effective Go](http://golang.org/doc/effective_go.html) - - - One major tool Rust has for accomplishing message sending concurrency is the *channel*, a programming concept that Rust’s standard library provides an implementation of. You can imagine a channel in programming like a channel of @@ -69,21 +66,11 @@ discussing the use of patterns in `let` statements and destructuring in Chapter 18. Using a `let` statement in this way is a convenient way 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 that the spawned thread is communicating with the main thread, 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 ```rust @@ -148,11 +135,6 @@ sent, `recv` will return it in a `Result`. When the sending end of the channel 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` 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 @@ -165,10 +147,6 @@ We’ve chosen to use `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. - - - If we run the code in Listing 16-8, we’ll see the value printed out from the main thread: @@ -180,18 +158,6 @@ Perfect! ### Channels and Ownership Transference - - - The ownership rules play a vital role in message sending as far as helping us write safe, concurrent code. Preventing errors in concurrent programming is the advantage we get by making the tradeoff of having to think about ownership @@ -313,15 +279,6 @@ 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 Near the start of this section, we mentioned that `mpsc` stood for *multiple diff --git a/second-edition/src/ch16-03-shared-state.md b/second-edition/src/ch16-03-shared-state.md index 8c2f28da1..0ef81c2f8 100644 --- a/second-edition/src/ch16-03-shared-state.md +++ b/second-edition/src/ch16-03-shared-state.md @@ -9,12 +9,6 @@ only one. Consider this slogan again: What would “communicate by sharing memory” look like? And moreover, why would message passing enthusiasts choose not to use it and do the opposite instead? - - - In a way, channels in any programming language are sort of like single ownership, because once you transfer a value down a channel, you shouldn’t use that value any longer. Shared memory concurrency is sort of like multiple @@ -40,10 +34,6 @@ data it holds via the locking system. Mutexes have a reputation for being hard to use because there are some rules you have to remember: - - - 1. You must attempt to acquire the lock before using the data. 2. Once you’re done with the data that’s guarded by the mutex, you must unlock the data so other threads can acquire the lock. @@ -91,18 +81,10 @@ To access the data inside the mutex, we use the `lock` method to acquire the lock. This call will block the current thread so that 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. - - - Once 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 this value: `Mutex` is not an `i32`, @@ -174,11 +156,6 @@ program. We hinted that this example won’t compile, now let’s find out why! - - - ```text error[E0382]: capture of moved value: `counter` --> @@ -339,11 +316,6 @@ Send is not satisfied`. We’re going to 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` is not safe to share across threads. When `Rc` 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 @@ -438,16 +410,5 @@ in any language, and have a go at implementing them in Rust. The standard library API documentation for `Mutex` and `MutexGuard` will have useful information. - - - - Let’s round out this chapter by talking about the `Send` and `Sync` traits and how we could use them with custom types.