1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-07-12 17:20:14 +02:00

Update Concurrency course with times (#2007)

As I mentioned in #1536:

* Break into segments at approximately the places @fw-immunant put
breaks
 * Move all of the files into `src/concurrency`
 * Add timings and segment/session metadata so course outlines appear

There's room for more work here, including some additional feedback from
@fw-immunant after the session I observed, but let's do one step at a
time :)
This commit is contained in:
Dustin J. Mitchell
2024-04-23 09:26:41 -04:00
committed by GitHub
parent a03b7e68e5
commit face5af783
58 changed files with 385 additions and 246 deletions

View File

@ -0,0 +1,43 @@
---
minutes: 5
---
# `Arc`
[`Arc<T>`][1] allows shared read-only access via `Arc::clone`:
```rust,editable
use std::sync::Arc;
use std::thread;
fn main() {
let v = Arc::new(vec![10, 20, 30]);
let mut handles = Vec::new();
for _ in 1..5 {
let v = Arc::clone(&v);
handles.push(thread::spawn(move || {
let thread_id = thread::current().id();
println!("{thread_id:?}: {v:?}");
}));
}
handles.into_iter().for_each(|h| h.join().unwrap());
println!("v: {v:?}");
}
```
[1]: https://doc.rust-lang.org/std/sync/struct.Arc.html
<details>
- `Arc` stands for "Atomic Reference Counted", a thread safe version of `Rc`
that uses atomic operations.
- `Arc<T>` implements `Clone` whether or not `T` does. It implements `Send` and
`Sync` if and only if `T` implements them both.
- `Arc::clone()` has the cost of atomic operations that get executed, but after
that the use of the `T` is free.
- Beware of reference cycles, `Arc` does not use a garbage collector to detect
them.
- `std::sync::Weak` can help.
</details>

View File

@ -0,0 +1,64 @@
---
minutes: 8
---
# Example
Let us see `Arc` and `Mutex` in action:
```rust,editable,compile_fail
use std::thread;
// use std::sync::{Arc, Mutex};
fn main() {
let v = vec![10, 20, 30];
let handle = thread::spawn(|| {
v.push(10);
});
v.push(1000);
handle.join().unwrap();
println!("v: {v:?}");
}
```
<details>
Possible solution:
```rust,editable
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let v = Arc::new(Mutex::new(vec![10, 20, 30]));
let v2 = Arc::clone(&v);
let handle = thread::spawn(move || {
let mut v2 = v2.lock().unwrap();
v2.push(10);
});
{
let mut v = v.lock().unwrap();
v.push(1000);
}
handle.join().unwrap();
println!("v: {v:?}");
}
```
Notable parts:
- `v` is wrapped in both `Arc` and `Mutex`, because their concerns are
orthogonal.
- Wrapping a `Mutex` in an `Arc` is a common pattern to share mutable state
between threads.
- `v: Arc<_>` needs to be cloned as `v2` before it can be moved into another
thread. Note `move` was added to the lambda signature.
- Blocks are introduced to narrow the scope of the `LockGuard` as much as
possible.
</details>

View File

@ -0,0 +1,53 @@
---
minutes: 14
---
# `Mutex`
[`Mutex<T>`][1] ensures mutual exclusion _and_ allows mutable access to `T`
behind a read-only interface (another form of
[interior mutability](../../borrowing/interior-mutability)):
```rust,editable
use std::sync::Mutex;
fn main() {
let v = Mutex::new(vec![10, 20, 30]);
println!("v: {:?}", v.lock().unwrap());
{
let mut guard = v.lock().unwrap();
guard.push(40);
}
println!("v: {:?}", v.lock().unwrap());
}
```
Notice how we have a [`impl<T: Send> Sync for Mutex<T>`][2] blanket
implementation.
[1]: https://doc.rust-lang.org/std/sync/struct.Mutex.html
[2]: https://doc.rust-lang.org/std/sync/struct.Mutex.html#impl-Sync-for-Mutex%3CT%3E
[3]: https://doc.rust-lang.org/std/sync/struct.Arc.html
<details>
- `Mutex` in Rust looks like a collection with just one element --- the
protected data.
- It is not possible to forget to acquire the mutex before accessing the
protected data.
- You can get an `&mut T` from an `&Mutex<T>` by taking the lock. The
`MutexGuard` ensures that the `&mut T` doesn't outlive the lock being held.
- `Mutex<T>` implements both `Send` and `Sync` iff (if and only if) `T`
implements `Send`.
- A read-write lock counterpart: `RwLock`.
- Why does `lock()` return a `Result`?
- If the thread that held the `Mutex` panicked, the `Mutex` becomes "poisoned"
to signal that the data it protected might be in an inconsistent state.
Calling `lock()` on a poisoned mutex fails with a [`PoisonError`]. You can
call `into_inner()` on the error to recover the data regardless.
[`PoisonError`]: https://doc.rust-lang.org/std/sync/struct.PoisonError.html
</details>