]>
Commit | Line | Data |
---|---|---|
83c7162d XL |
1 | ## Shared-State Concurrency |
2 | ||
3 | Message passing is a fine way of handling concurrency, but it’s not the only | |
4 | one. Consider this part of the slogan from the Go language documentation again: | |
5 | “communicate by sharing memory.” | |
6 | ||
7 | What would communicating by sharing memory look like? In addition, why would | |
8 | message-passing enthusiasts not use it and do the opposite instead? | |
9 | ||
10 | In a way, channels in any programming language are similar to single ownership, | |
11 | because once you transfer a value down a channel, you should no longer use that | |
12 | value. Shared memory concurrency is like multiple ownership: multiple threads | |
13 | can access the same memory location at the same time. As you saw in Chapter 15, | |
14 | where smart pointers made multiple ownership possible, multiple ownership can | |
15 | add complexity because these different owners need managing. Rust’s type system | |
16 | and ownership rules greatly assist in getting this management correct. For an | |
17 | example, let’s look at mutexes, one of the more common concurrency primitives | |
18 | for shared memory. | |
19 | ||
20 | ### Using Mutexes to Allow Access to Data from One Thread at a Time | |
21 | ||
22 | *Mutex* is an abbreviation for *mutual exclusion*, as in, a mutex allows only | |
23 | one thread to access some data at any given time. To access the data in a | |
24 | mutex, a thread must first signal that it wants access by asking to acquire the | |
25 | mutex’s *lock*. The lock is a data structure that is part of the mutex that | |
26 | keeps track of who currently has exclusive access to the data. Therefore, the | |
27 | mutex is described as *guarding* the data it holds via the locking system. | |
28 | ||
29 | Mutexes have a reputation for being difficult to use because you have to | |
30 | remember two rules: | |
31 | ||
32 | * You must attempt to acquire the lock before using the data. | |
33 | * When you’re done with the data that the mutex guards, you must unlock the | |
34 | data so other threads can acquire the lock. | |
35 | ||
36 | For a real-world metaphor for a mutex, imagine a panel discussion at a | |
37 | conference with only one microphone. Before a panelist can speak, they have to | |
38 | ask or signal that they want to use the microphone. When they get the | |
39 | microphone, they can talk for as long as they want to and then hand the | |
40 | microphone to the next panelist who requests to speak. If a panelist forgets to | |
41 | hand the microphone off when they’re finished with it, no one else is able to | |
42 | speak. If management of the shared microphone goes wrong, the panel won’t work | |
43 | as planned! | |
44 | ||
45 | Management of mutexes can be incredibly tricky to get right, which is why so | |
46 | many people are enthusiastic about channels. However, thanks to Rust’s type | |
47 | system and ownership rules, you can’t get locking and unlocking wrong. | |
48 | ||
49 | #### The API of `Mutex<T>` | |
50 | ||
51 | As an example of how to use a mutex, let’s start by using a mutex in a | |
52 | single-threaded context, as shown in Listing 16-12: | |
53 | ||
54 | <span class="filename">Filename: src/main.rs</span> | |
55 | ||
56 | ```rust | |
57 | use std::sync::Mutex; | |
58 | ||
59 | fn main() { | |
60 | let m = Mutex::new(5); | |
61 | ||
62 | { | |
63 | let mut num = m.lock().unwrap(); | |
64 | *num = 6; | |
65 | } | |
66 | ||
67 | println!("m = {:?}", m); | |
68 | } | |
69 | ``` | |
70 | ||
71 | <span class="caption">Listing 16-12: Exploring the API of `Mutex<T>` in a | |
72 | single-threaded context for simplicity</span> | |
73 | ||
74 | As with many types, we create a `Mutex<T>` using the associated function `new`. | |
75 | To access the data inside the mutex, we use the `lock` method to acquire the | |
76 | lock. This call will block the current thread so it can’t do any work until | |
77 | it’s our turn to have the lock. | |
78 | ||
79 | The call to `lock` would fail if another thread holding the lock panicked. In | |
80 | that case, no one would ever be able to get the lock, so we’ve chosen to | |
81 | `unwrap` and have this thread panic if we’re in that situation. | |
82 | ||
83 | After we’ve acquired the lock, we can treat the return value, named `num` in | |
84 | this case, as a mutable reference to the data inside. The type system ensures | |
85 | that we acquire a lock before using the value in `m`: `Mutex<i32>` is not an | |
86 | `i32`, so we *must* acquire the lock to be able to use the `i32` value. We | |
87 | can’t forget; the type system won’t let us access the inner `i32` otherwise. | |
88 | ||
89 | As you might suspect, `Mutex<T>` is a smart pointer. More accurately, the call | |
90 | to `lock` *returns* a smart pointer called `MutexGuard`. This smart pointer | |
91 | implements `Deref` to point at our inner data; the smart pointer also has a | |
92 | `Drop` implementation that releases the lock automatically when a `MutexGuard` | |
93 | goes out of scope, which happens at the end of the inner scope in Listing | |
94 | 16-12. As a result, we don’t risk forgetting to release the lock and blocking | |
95 | the mutex from being used by other threads because the lock release happens | |
96 | automatically. | |
97 | ||
98 | After dropping the lock, we can print the mutex value and see that we were able | |
99 | to change the inner `i32` to 6. | |
100 | ||
101 | #### Sharing a `Mutex<T>` Between Multiple Threads | |
102 | ||
103 | Now, let’s try to share a value between multiple threads using `Mutex<T>`. | |
104 | We’ll spin up 10 threads and have them each increment a counter value by 1, so | |
105 | the counter goes from 0 to 10. Note that the next few examples will have | |
106 | compiler errors, and we’ll use those errors to learn more about using | |
107 | `Mutex<T>` and how Rust helps us use it correctly. Listing 16-13 has our | |
108 | starting example: | |
109 | ||
110 | <span class="filename">Filename: src/main.rs</span> | |
111 | ||
0bf4aa26 | 112 | ```rust,ignore,does_not_compile |
83c7162d XL |
113 | use std::sync::Mutex; |
114 | use std::thread; | |
115 | ||
116 | fn main() { | |
117 | let counter = Mutex::new(0); | |
118 | let mut handles = vec![]; | |
119 | ||
120 | for _ in 0..10 { | |
121 | let handle = thread::spawn(move || { | |
122 | let mut num = counter.lock().unwrap(); | |
123 | ||
124 | *num += 1; | |
125 | }); | |
126 | handles.push(handle); | |
127 | } | |
128 | ||
129 | for handle in handles { | |
130 | handle.join().unwrap(); | |
131 | } | |
132 | ||
133 | println!("Result: {}", *counter.lock().unwrap()); | |
134 | } | |
135 | ``` | |
136 | ||
137 | <span class="caption">Listing 16-13: Ten threads each increment a counter | |
138 | guarded by a `Mutex<T>`</span> | |
139 | ||
140 | We create a `counter` variable to hold an `i32` inside a `Mutex<T>`, as we | |
141 | did in Listing 16-12. Next, we create 10 threads by iterating over a range | |
142 | of numbers. We use `thread::spawn` and give all the threads the same closure, | |
143 | one that moves the counter into the thread, acquires a lock on the `Mutex<T>` | |
144 | by calling the `lock` method, and then adds 1 to the value in the mutex. When a | |
145 | thread finishes running its closure, `num` will go out of scope and release the | |
146 | lock so another thread can acquire it. | |
147 | ||
148 | In the main thread, we collect all the join handles. Then, as we did in Listing | |
149 | 16-2, we call `join` on each handle to make sure all the threads finish. At | |
150 | that point, the main thread will acquire the lock and print the result of this | |
151 | program. | |
152 | ||
153 | We hinted that this example wouldn’t compile. Now let’s find out why! | |
154 | ||
155 | ```text | |
156 | error[E0382]: capture of moved value: `counter` | |
157 | --> src/main.rs:10:27 | |
158 | | | |
159 | 9 | let handle = thread::spawn(move || { | |
160 | | ------- value moved (into closure) here | |
161 | 10 | let mut num = counter.lock().unwrap(); | |
162 | | ^^^^^^^ value captured here after move | |
163 | | | |
164 | = note: move occurs because `counter` has type `std::sync::Mutex<i32>`, | |
165 | which does not implement the `Copy` trait | |
166 | ||
167 | error[E0382]: use of moved value: `counter` | |
168 | --> src/main.rs:21:29 | |
169 | | | |
170 | 9 | let handle = thread::spawn(move || { | |
171 | | ------- value moved (into closure) here | |
172 | ... | |
173 | 21 | println!("Result: {}", *counter.lock().unwrap()); | |
174 | | ^^^^^^^ value used here after move | |
175 | | | |
176 | = note: move occurs because `counter` has type `std::sync::Mutex<i32>`, | |
177 | which does not implement the `Copy` trait | |
178 | ||
179 | error: aborting due to 2 previous errors | |
180 | ``` | |
181 | ||
182 | The error message states that the `counter` value is moved into the closure and | |
183 | then captured when we call `lock`. That description sounds like what we wanted, | |
184 | but it’s not allowed! | |
185 | ||
186 | Let’s figure this out by simplifying the program. Instead of making 10 threads | |
187 | in a `for` loop, let’s just make two threads without a loop and see what | |
188 | happens. Replace the first `for` loop in Listing 16-13 with this code instead: | |
189 | ||
0bf4aa26 | 190 | ```rust,ignore,does_not_compile |
83c7162d XL |
191 | use std::sync::Mutex; |
192 | use std::thread; | |
193 | ||
194 | fn main() { | |
195 | let counter = Mutex::new(0); | |
196 | let mut handles = vec![]; | |
197 | ||
198 | let handle = thread::spawn(move || { | |
199 | let mut num = counter.lock().unwrap(); | |
200 | ||
201 | *num += 1; | |
202 | }); | |
203 | handles.push(handle); | |
204 | ||
205 | let handle2 = thread::spawn(move || { | |
206 | let mut num2 = counter.lock().unwrap(); | |
207 | ||
208 | *num2 += 1; | |
209 | }); | |
210 | handles.push(handle2); | |
211 | ||
212 | for handle in handles { | |
213 | handle.join().unwrap(); | |
214 | } | |
215 | ||
216 | println!("Result: {}", *counter.lock().unwrap()); | |
217 | } | |
218 | ``` | |
219 | ||
220 | We make two threads and change the variable names used with the second thread | |
221 | to `handle2` and `num2`. When we run the code this time, compiling gives us the | |
222 | following: | |
223 | ||
224 | ```text | |
225 | error[E0382]: capture of moved value: `counter` | |
226 | --> src/main.rs:16:24 | |
227 | | | |
228 | 8 | let handle = thread::spawn(move || { | |
229 | | ------- value moved (into closure) here | |
230 | ... | |
231 | 16 | let mut num2 = counter.lock().unwrap(); | |
232 | | ^^^^^^^ value captured here after move | |
233 | | | |
234 | = note: move occurs because `counter` has type `std::sync::Mutex<i32>`, | |
235 | which does not implement the `Copy` trait | |
236 | ||
237 | error[E0382]: use of moved value: `counter` | |
238 | --> src/main.rs:26:29 | |
239 | | | |
240 | 8 | let handle = thread::spawn(move || { | |
241 | | ------- value moved (into closure) here | |
242 | ... | |
243 | 26 | println!("Result: {}", *counter.lock().unwrap()); | |
244 | | ^^^^^^^ value used here after move | |
245 | | | |
246 | = note: move occurs because `counter` has type `std::sync::Mutex<i32>`, | |
247 | which does not implement the `Copy` trait | |
248 | ||
249 | error: aborting due to 2 previous errors | |
250 | ``` | |
251 | ||
252 | Aha! The first error message indicates that `counter` is moved into the closure | |
253 | for the thread associated with `handle`. That move is preventing us from | |
254 | capturing `counter` when we try to call `lock` on it and store the result in | |
255 | `num2` in the second thread! So Rust is telling us that we can’t move ownership | |
256 | of `counter` into multiple threads. This was hard to see earlier because our | |
257 | threads were in a loop, and Rust can’t point to different threads in different | |
258 | iterations of the loop. Let’s fix the compiler error with a multiple-ownership | |
259 | method we discussed in Chapter 15. | |
260 | ||
261 | #### Multiple Ownership with Multiple Threads | |
262 | ||
263 | In Chapter 15, we gave a value multiple owners by using the smart pointer | |
264 | `Rc<T>` to create a reference counted value. Let’s do the same here and see | |
265 | what happens. We’ll wrap the `Mutex<T>` in `Rc<T>` in Listing 16-14 and clone | |
266 | the `Rc<T>` before moving ownership to the thread. Now that we’ve seen the | |
267 | errors, we’ll also switch back to using the `for` loop, and we’ll keep the | |
268 | `move` keyword with the closure. | |
269 | ||
270 | <span class="filename">Filename: src/main.rs</span> | |
271 | ||
0bf4aa26 | 272 | ```rust,ignore,does_not_compile |
83c7162d XL |
273 | use std::rc::Rc; |
274 | use std::sync::Mutex; | |
275 | use std::thread; | |
276 | ||
277 | fn main() { | |
278 | let counter = Rc::new(Mutex::new(0)); | |
279 | let mut handles = vec![]; | |
280 | ||
281 | for _ in 0..10 { | |
282 | let counter = Rc::clone(&counter); | |
283 | let handle = thread::spawn(move || { | |
284 | let mut num = counter.lock().unwrap(); | |
285 | ||
286 | *num += 1; | |
287 | }); | |
288 | handles.push(handle); | |
289 | } | |
290 | ||
291 | for handle in handles { | |
292 | handle.join().unwrap(); | |
293 | } | |
294 | ||
295 | println!("Result: {}", *counter.lock().unwrap()); | |
296 | } | |
297 | ``` | |
298 | ||
299 | <span class="caption">Listing 16-14: Attempting to use `Rc<T>` to allow | |
300 | multiple threads to own the `Mutex<T>`</span> | |
301 | ||
302 | Once again, we compile and get... different errors! The compiler is teaching us | |
303 | a lot. | |
304 | ||
305 | ```text | |
306 | error[E0277]: the trait bound `std::rc::Rc<std::sync::Mutex<i32>>: | |
307 | std::marker::Send` is not satisfied in `[closure@src/main.rs:11:36: | |
308 | 15:10 counter:std::rc::Rc<std::sync::Mutex<i32>>]` | |
309 | --> src/main.rs:11:22 | |
310 | | | |
311 | 11 | let handle = thread::spawn(move || { | |
312 | | ^^^^^^^^^^^^^ `std::rc::Rc<std::sync::Mutex<i32>>` | |
313 | cannot be sent between threads safely | |
314 | | | |
315 | = help: within `[closure@src/main.rs:11:36: 15:10 | |
316 | counter:std::rc::Rc<std::sync::Mutex<i32>>]`, the trait `std::marker::Send` is | |
317 | not implemented for `std::rc::Rc<std::sync::Mutex<i32>>` | |
318 | = note: required because it appears within the type | |
319 | `[closure@src/main.rs:11:36: 15:10 counter:std::rc::Rc<std::sync::Mutex<i32>>]` | |
320 | = note: required by `std::thread::spawn` | |
321 | ``` | |
322 | ||
323 | Wow, that error message is very wordy! Here are some important parts to focus | |
324 | on: the first inline error says `` `std::rc::Rc<std::sync::Mutex<i32>>` cannot | |
325 | be sent between threads safely ``. The reason for this is in the next important | |
326 | part to focus on, the error message. The distilled error message says `` the | |
327 | trait bound `Send` is not satisfied ``. We’ll talk about `Send` in the next | |
328 | section: it’s one of the traits that ensures the types we use with threads are | |
329 | meant for use in concurrent situations. | |
330 | ||
331 | Unfortunately, `Rc<T>` is not safe to share across threads. When `Rc<T>` | |
332 | manages the reference count, it adds to the count for each call to `clone` and | |
333 | subtracts from the count when each clone is dropped. But it doesn’t use any | |
334 | concurrency primitives to make sure that changes to the count can’t be | |
335 | interrupted by another thread. This could lead to wrong counts—subtle bugs that | |
336 | could in turn lead to memory leaks or a value being dropped before we’re done | |
337 | with it. What we need is a type exactly like `Rc<T>` but one that makes changes | |
338 | to the reference count in a thread-safe way. | |
339 | ||
340 | #### Atomic Reference Counting with `Arc<T>` | |
341 | ||
342 | Fortunately, `Arc<T>` *is* a type like `Rc<T>` that is safe to use in | |
343 | concurrent situations. The *a* stands for *atomic*, meaning it’s an *atomically | |
344 | reference counted* type. Atomics are an additional kind of concurrency | |
345 | primitive that we won’t cover in detail here: see the standard library | |
346 | documentation for `std::sync::atomic` for more details. At this point, you just | |
347 | need to know that atomics work like primitive types but are safe to share | |
348 | across threads. | |
349 | ||
350 | You might then wonder why all primitive types aren’t atomic and why standard | |
351 | library types aren’t implemented to use `Arc<T>` by default. The reason is that | |
352 | thread safety comes with a performance penalty that you only want to pay when | |
353 | you really need to. If you’re just performing operations on values within a | |
354 | single thread, your code can run faster if it doesn’t have to enforce the | |
355 | guarantees atomics provide. | |
356 | ||
357 | Let’s return to our example: `Arc<T>` and `Rc<T>` have the same API, so we fix | |
358 | our program by changing the `use` line, the call to `new`, and the call to | |
359 | `clone`. The code in Listing 16-15 will finally compile and run: | |
360 | ||
361 | <span class="filename">Filename: src/main.rs</span> | |
362 | ||
363 | ```rust | |
364 | use std::sync::{Mutex, Arc}; | |
365 | use std::thread; | |
366 | ||
367 | fn main() { | |
368 | let counter = Arc::new(Mutex::new(0)); | |
369 | let mut handles = vec![]; | |
370 | ||
371 | for _ in 0..10 { | |
372 | let counter = Arc::clone(&counter); | |
373 | let handle = thread::spawn(move || { | |
374 | let mut num = counter.lock().unwrap(); | |
375 | ||
376 | *num += 1; | |
377 | }); | |
378 | handles.push(handle); | |
379 | } | |
380 | ||
381 | for handle in handles { | |
382 | handle.join().unwrap(); | |
383 | } | |
384 | ||
385 | println!("Result: {}", *counter.lock().unwrap()); | |
386 | } | |
387 | ``` | |
388 | ||
389 | <span class="caption">Listing 16-15: Using an `Arc<T>` to wrap the `Mutex<T>` | |
390 | to be able to share ownership across multiple threads</span> | |
391 | ||
392 | This code will print the following: | |
393 | ||
394 | ```text | |
395 | Result: 10 | |
396 | ``` | |
397 | ||
398 | We did it! We counted from 0 to 10, which may not seem very impressive, but it | |
399 | did teach us a lot about `Mutex<T>` and thread safety. You could also use this | |
400 | program’s structure to do more complicated operations than just incrementing a | |
401 | counter. Using this strategy, you can divide a calculation into independent | |
402 | parts, split those parts across threads, and then use a `Mutex<T>` to have each | |
403 | thread update the final result with its part. | |
404 | ||
405 | ### Similarities Between `RefCell<T>`/`Rc<T>` and `Mutex<T>`/`Arc<T>` | |
406 | ||
407 | You might have noticed that `counter` is immutable but we could get a mutable | |
408 | reference to the value inside it; this means `Mutex<T>` provides interior | |
409 | mutability, as the `Cell` family does. In the same way we used `RefCell<T>` in | |
410 | Chapter 15 to allow us to mutate contents inside an `Rc<T>`, we use `Mutex<T>` | |
411 | to mutate contents inside an `Arc<T>`. | |
412 | ||
413 | Another detail to note is that Rust can’t protect you from all kinds of logic | |
414 | errors when you use `Mutex<T>`. Recall in Chapter 15 that using `Rc<T>` came | |
415 | with the risk of creating reference cycles, where two `Rc<T>` values refer to | |
416 | each other, causing memory leaks. Similarly, `Mutex<T>` comes with the risk of | |
417 | creating *deadlocks*. These occur when an operation needs to lock two resources | |
418 | and two threads have each acquired one of the locks, causing them to wait for | |
419 | each other forever. If you’re interested in deadlocks, try creating a Rust | |
420 | program that has a deadlock; then research deadlock mitigation strategies for | |
421 | mutexes in any language and have a go at implementing them in Rust. The | |
422 | standard library API documentation for `Mutex<T>` and `MutexGuard` offers | |
423 | useful information. | |
424 | ||
425 | We’ll round out this chapter by talking about the `Send` and `Sync` traits and | |
426 | how we can use them with custom types. |