]>
Commit | Line | Data |
---|---|---|
85aaf69f SL |
1 | % Concurrency |
2 | ||
3 | Concurrency and parallelism are incredibly important topics in computer | |
4 | science, and are also a hot topic in industry today. Computers are gaining more | |
5 | and more cores, yet many programmers aren't prepared to fully utilize them. | |
6 | ||
7 | Rust's memory safety features also apply to its concurrency story too. Even | |
8 | concurrent Rust programs must be memory safe, having no data races. Rust's type | |
9 | system is up to the task, and gives you powerful ways to reason about | |
10 | concurrent code at compile time. | |
11 | ||
12 | Before we talk about the concurrency features that come with Rust, it's important | |
13 | to understand something: Rust is low-level enough that all of this is provided | |
14 | by the standard library, not by the language. This means that if you don't like | |
15 | some aspect of the way Rust handles concurrency, you can implement an alternative | |
16 | way of doing things. [mio](https://github.com/carllerche/mio) is a real-world | |
17 | example of this principle in action. | |
18 | ||
19 | ## Background: `Send` and `Sync` | |
20 | ||
21 | Concurrency is difficult to reason about. In Rust, we have a strong, static | |
22 | type system to help us reason about our code. As such, Rust gives us two traits | |
23 | to help us make sense of code that can possibly be concurrent. | |
24 | ||
25 | ### `Send` | |
26 | ||
27 | The first trait we're going to talk about is | |
28 | [`Send`](../std/marker/trait.Send.html). When a type `T` implements `Send`, it indicates | |
29 | to the compiler that something of this type is able to have ownership transferred | |
30 | safely between threads. | |
31 | ||
32 | This is important to enforce certain restrictions. For example, if we have a | |
33 | channel connecting two threads, we would want to be able to send some data | |
34 | down the channel and to the other thread. Therefore, we'd ensure that `Send` was | |
35 | implemented for that type. | |
36 | ||
37 | In the opposite way, if we were wrapping a library with FFI that isn't | |
38 | threadsafe, we wouldn't want to implement `Send`, and so the compiler will help | |
39 | us enforce that it can't leave the current thread. | |
40 | ||
41 | ### `Sync` | |
42 | ||
c34b1796 | 43 | The second of these traits is called [`Sync`](../std/marker/trait.Sync.html). |
85aaf69f SL |
44 | When a type `T` implements `Sync`, it indicates to the compiler that something |
45 | of this type has no possibility of introducing memory unsafety when used from | |
46 | multiple threads concurrently. | |
47 | ||
48 | For example, sharing immutable data with an atomic reference count is | |
49 | threadsafe. Rust provides a type like this, `Arc<T>`, and it implements `Sync`, | |
c34b1796 | 50 | so it is safe to share between threads. |
85aaf69f SL |
51 | |
52 | These two traits allow you to use the type system to make strong guarantees | |
53 | about the properties of your code under concurrency. Before we demonstrate | |
54 | why, we need to learn how to create a concurrent Rust program in the first | |
55 | place! | |
56 | ||
57 | ## Threads | |
58 | ||
9346a6ac | 59 | Rust's standard library provides a library for threads, which allow you to |
85aaf69f SL |
60 | run Rust code in parallel. Here's a basic example of using `std::thread`: |
61 | ||
62 | ``` | |
63 | use std::thread; | |
64 | ||
65 | fn main() { | |
9346a6ac | 66 | thread::spawn(|| { |
85aaf69f SL |
67 | println!("Hello from a thread!"); |
68 | }); | |
69 | } | |
70 | ``` | |
71 | ||
9346a6ac AL |
72 | The `thread::spawn()` method accepts a closure, which is executed in a |
73 | new thread. It returns a handle to the thread, that can be used to | |
74 | wait for the child thread to finish and extract its result: | |
85aaf69f SL |
75 | |
76 | ``` | |
77 | use std::thread; | |
78 | ||
79 | fn main() { | |
9346a6ac AL |
80 | let handle = thread::spawn(|| { |
81 | "Hello from a thread!" | |
85aaf69f SL |
82 | }); |
83 | ||
9346a6ac | 84 | println!("{}", handle.join().unwrap()); |
85aaf69f SL |
85 | } |
86 | ``` | |
87 | ||
85aaf69f SL |
88 | Many languages have the ability to execute threads, but it's wildly unsafe. |
89 | There are entire books about how to prevent errors that occur from shared | |
90 | mutable state. Rust helps out with its type system here as well, by preventing | |
91 | data races at compile time. Let's talk about how you actually share things | |
92 | between threads. | |
93 | ||
94 | ## Safe Shared Mutable State | |
95 | ||
96 | Due to Rust's type system, we have a concept that sounds like a lie: "safe | |
97 | shared mutable state." Many programmers agree that shared mutable state is | |
98 | very, very bad. | |
99 | ||
100 | Someone once said this: | |
101 | ||
102 | > Shared mutable state is the root of all evil. Most languages attempt to deal | |
103 | > with this problem through the 'mutable' part, but Rust deals with it by | |
104 | > solving the 'shared' part. | |
105 | ||
106 | The same [ownership system](ownership.html) that helps prevent using pointers | |
107 | incorrectly also helps rule out data races, one of the worst kinds of | |
108 | concurrency bugs. | |
109 | ||
110 | As an example, here is a Rust program that would have a data race in many | |
111 | languages. It will not compile: | |
112 | ||
113 | ```ignore | |
114 | use std::thread; | |
85aaf69f SL |
115 | |
116 | fn main() { | |
117 | let mut data = vec![1u32, 2, 3]; | |
118 | ||
119 | for i in 0..2 { | |
120 | thread::spawn(move || { | |
121 | data[i] += 1; | |
122 | }); | |
123 | } | |
124 | ||
9346a6ac | 125 | thread::sleep_ms(50); |
85aaf69f SL |
126 | } |
127 | ``` | |
128 | ||
129 | This gives us an error: | |
130 | ||
131 | ```text | |
9346a6ac | 132 | 8:17 error: capture of moved value: `data` |
85aaf69f SL |
133 | data[i] += 1; |
134 | ^~~~ | |
135 | ``` | |
136 | ||
137 | In this case, we know that our code _should_ be safe, but Rust isn't sure. And | |
138 | it's actually not safe: if we had a reference to `data` in each thread, and the | |
139 | thread takes ownership of the reference, we have three owners! That's bad. We | |
140 | can fix this by using the `Arc<T>` type, which is an atomic reference counted | |
141 | pointer. The 'atomic' part means that it's safe to share across threads. | |
142 | ||
143 | `Arc<T>` assumes one more property about its contents to ensure that it is safe | |
144 | to share across threads: it assumes its contents are `Sync`. But in our | |
145 | case, we want to be able to mutate the value. We need a type that can ensure | |
146 | only one person at a time can mutate what's inside. For that, we can use the | |
147 | `Mutex<T>` type. Here's the second version of our code. It still doesn't work, | |
148 | but for a different reason: | |
149 | ||
150 | ```ignore | |
151 | use std::thread; | |
85aaf69f SL |
152 | use std::sync::Mutex; |
153 | ||
154 | fn main() { | |
155 | let mut data = Mutex::new(vec![1u32, 2, 3]); | |
156 | ||
157 | for i in 0..2 { | |
158 | let data = data.lock().unwrap(); | |
159 | thread::spawn(move || { | |
160 | data[i] += 1; | |
161 | }); | |
162 | } | |
163 | ||
9346a6ac | 164 | thread::sleep_ms(50); |
85aaf69f SL |
165 | } |
166 | ``` | |
167 | ||
168 | Here's the error: | |
169 | ||
170 | ```text | |
9346a6ac | 171 | <anon>:9:9: 9:22 error: the trait `core::marker::Send` is not implemented for the type `std::sync::mutex::MutexGuard<'_, collections::vec::Vec<u32>>` [E0277] |
c34b1796 | 172 | <anon>:11 thread::spawn(move || { |
85aaf69f | 173 | ^~~~~~~~~~~~~ |
9346a6ac | 174 | <anon>:9:9: 9:22 note: `std::sync::mutex::MutexGuard<'_, collections::vec::Vec<u32>>` cannot be sent between threads safely |
c34b1796 | 175 | <anon>:11 thread::spawn(move || { |
85aaf69f SL |
176 | ^~~~~~~~~~~~~ |
177 | ``` | |
178 | ||
179 | You see, [`Mutex`](std/sync/struct.Mutex.html) has a | |
180 | [`lock`](http://doc.rust-lang.org/nightly/std/sync/struct.Mutex.html#method.lock) | |
181 | method which has this signature: | |
182 | ||
183 | ```ignore | |
184 | fn lock(&self) -> LockResult<MutexGuard<T>> | |
185 | ``` | |
186 | ||
c34b1796 AL |
187 | Because `Send` is not implemented for `MutexGuard<T>`, we can't transfer the |
188 | guard across thread boundaries, which gives us our error. | |
85aaf69f SL |
189 | |
190 | We can use `Arc<T>` to fix this. Here's the working version: | |
191 | ||
192 | ``` | |
193 | use std::sync::{Arc, Mutex}; | |
194 | use std::thread; | |
85aaf69f SL |
195 | |
196 | fn main() { | |
197 | let data = Arc::new(Mutex::new(vec![1u32, 2, 3])); | |
198 | ||
199 | for i in 0..2 { | |
200 | let data = data.clone(); | |
201 | thread::spawn(move || { | |
202 | let mut data = data.lock().unwrap(); | |
203 | data[i] += 1; | |
204 | }); | |
205 | } | |
206 | ||
9346a6ac | 207 | thread::sleep_ms(50); |
85aaf69f SL |
208 | } |
209 | ``` | |
210 | ||
211 | We now call `clone()` on our `Arc`, which increases the internal count. This | |
212 | handle is then moved into the new thread. Let's examine the body of the | |
213 | thread more closely: | |
214 | ||
9346a6ac | 215 | ```rust |
85aaf69f SL |
216 | # use std::sync::{Arc, Mutex}; |
217 | # use std::thread; | |
85aaf69f SL |
218 | # fn main() { |
219 | # let data = Arc::new(Mutex::new(vec![1u32, 2, 3])); | |
220 | # for i in 0..2 { | |
221 | # let data = data.clone(); | |
222 | thread::spawn(move || { | |
223 | let mut data = data.lock().unwrap(); | |
224 | data[i] += 1; | |
225 | }); | |
226 | # } | |
9346a6ac | 227 | # thread::sleep_ms(50); |
85aaf69f SL |
228 | # } |
229 | ``` | |
230 | ||
231 | First, we call `lock()`, which acquires the mutex's lock. Because this may fail, | |
232 | it returns an `Result<T, E>`, and because this is just an example, we `unwrap()` | |
233 | it to get a reference to the data. Real code would have more robust error handling | |
234 | here. We're then free to mutate it, since we have the lock. | |
235 | ||
c34b1796 AL |
236 | Lastly, while the threads are running, we wait on a short timer. But |
237 | this is not ideal: we may have picked a reasonable amount of time to | |
238 | wait but it's more likely we'll either be waiting longer than | |
239 | necessary or not long enough, depending on just how much time the | |
240 | threads actually take to finish computing when the program runs. | |
85aaf69f | 241 | |
c34b1796 AL |
242 | A more precise alternative to the timer would be to use one of the |
243 | mechanisms provided by the Rust standard library for synchronizing | |
244 | threads with each other. Let's talk about one of them: channels. | |
85aaf69f SL |
245 | |
246 | ## Channels | |
247 | ||
248 | Here's a version of our code that uses channels for synchronization, rather | |
249 | than waiting for a specific time: | |
250 | ||
251 | ``` | |
252 | use std::sync::{Arc, Mutex}; | |
253 | use std::thread; | |
254 | use std::sync::mpsc; | |
255 | ||
256 | fn main() { | |
257 | let data = Arc::new(Mutex::new(0u32)); | |
258 | ||
259 | let (tx, rx) = mpsc::channel(); | |
260 | ||
261 | for _ in 0..10 { | |
262 | let (data, tx) = (data.clone(), tx.clone()); | |
263 | ||
264 | thread::spawn(move || { | |
265 | let mut data = data.lock().unwrap(); | |
266 | *data += 1; | |
267 | ||
268 | tx.send(()); | |
269 | }); | |
270 | } | |
271 | ||
272 | for _ in 0..10 { | |
273 | rx.recv(); | |
274 | } | |
275 | } | |
276 | ``` | |
277 | ||
278 | We use the `mpsc::channel()` method to construct a new channel. We just `send` | |
279 | a simple `()` down the channel, and then wait for ten of them to come back. | |
280 | ||
281 | While this channel is just sending a generic signal, we can send any data that | |
282 | is `Send` over the channel! | |
283 | ||
284 | ``` | |
85aaf69f SL |
285 | use std::thread; |
286 | use std::sync::mpsc; | |
287 | ||
288 | fn main() { | |
289 | let (tx, rx) = mpsc::channel(); | |
290 | ||
291 | for _ in 0..10 { | |
292 | let tx = tx.clone(); | |
293 | ||
294 | thread::spawn(move || { | |
295 | let answer = 42u32; | |
296 | ||
297 | tx.send(answer); | |
298 | }); | |
299 | } | |
300 | ||
c34b1796 | 301 | rx.recv().ok().expect("Could not receive answer"); |
85aaf69f SL |
302 | } |
303 | ``` | |
304 | ||
305 | A `u32` is `Send` because we can make a copy. So we create a thread, ask it to calculate | |
306 | the answer, and then it `send()`s us the answer over the channel. | |
307 | ||
308 | ||
309 | ## Panics | |
310 | ||
311 | A `panic!` will crash the currently executing thread. You can use Rust's | |
312 | threads as a simple isolation mechanism: | |
313 | ||
314 | ``` | |
315 | use std::thread; | |
316 | ||
317 | let result = thread::spawn(move || { | |
318 | panic!("oops!"); | |
319 | }).join(); | |
320 | ||
321 | assert!(result.is_err()); | |
322 | ``` | |
323 | ||
324 | Our `Thread` gives us a `Result` back, which allows us to check if the thread | |
325 | has panicked or not. |