You've already forked comprehensive-rust
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:
committed by
GitHub
parent
a03b7e68e5
commit
face5af783
43
src/concurrency/shared-state/arc.md
Normal file
43
src/concurrency/shared-state/arc.md
Normal 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>
|
64
src/concurrency/shared-state/example.md
Normal file
64
src/concurrency/shared-state/example.md
Normal 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>
|
53
src/concurrency/shared-state/mutex.md
Normal file
53
src/concurrency/shared-state/mutex.md
Normal 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>
|
Reference in New Issue
Block a user