mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 13:04:18 -06:00
finished ch16.3
This commit is contained in:
parent
96dbd779e7
commit
084ac0ecff
48
.obsidian/workspace.json
vendored
48
.obsidian/workspace.json
vendored
@ -35,6 +35,20 @@
|
|||||||
"title": "Concurrency"
|
"title": "Concurrency"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "2a974ca5442d705f",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "markdown",
|
||||||
|
"state": {
|
||||||
|
"file": "Sync and Send.md",
|
||||||
|
"mode": "source",
|
||||||
|
"source": false
|
||||||
|
},
|
||||||
|
"icon": "lucide-file",
|
||||||
|
"title": "Sync and Send"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "3d0ca0b1691c4c2f",
|
"id": "3d0ca0b1691c4c2f",
|
||||||
"type": "leaf",
|
"type": "leaf",
|
||||||
@ -63,34 +77,6 @@
|
|||||||
"title": "Leaky Reference Cycles"
|
"title": "Leaky Reference Cycles"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "74da570aae42b3e7",
|
|
||||||
"type": "leaf",
|
|
||||||
"state": {
|
|
||||||
"type": "markdown",
|
|
||||||
"state": {
|
|
||||||
"file": "Ref Cell Mutability.md",
|
|
||||||
"mode": "source",
|
|
||||||
"source": false
|
|
||||||
},
|
|
||||||
"icon": "lucide-file",
|
|
||||||
"title": "Ref Cell Mutability"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7615e7dc6a30893a",
|
|
||||||
"type": "leaf",
|
|
||||||
"state": {
|
|
||||||
"type": "markdown",
|
|
||||||
"state": {
|
|
||||||
"file": "Reference Counter Smart Pointer.md",
|
|
||||||
"mode": "source",
|
|
||||||
"source": false
|
|
||||||
},
|
|
||||||
"icon": "lucide-file",
|
|
||||||
"title": "Reference Counter Smart Pointer"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "6ed9f182aa3d5f3e",
|
"id": "6ed9f182aa3d5f3e",
|
||||||
"type": "leaf",
|
"type": "leaf",
|
||||||
@ -245,12 +231,13 @@
|
|||||||
"command-palette:Open command palette": false
|
"command-palette:Open command palette": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"active": "3d0ca0b1691c4c2f",
|
"active": "2a974ca5442d705f",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
"Concurrency.md",
|
"Concurrency.md",
|
||||||
|
"Sync and Send.md",
|
||||||
"Shared State Concurrency.md",
|
"Shared State Concurrency.md",
|
||||||
"Smart Pointers.md",
|
|
||||||
"().md",
|
"().md",
|
||||||
|
"Smart Pointers.md",
|
||||||
"Simultaneous Code Running.md",
|
"Simultaneous Code Running.md",
|
||||||
"Passing Data Between Threads.md",
|
"Passing Data Between Threads.md",
|
||||||
"Leaky Reference Cycles.md",
|
"Leaky Reference Cycles.md",
|
||||||
@ -273,7 +260,6 @@
|
|||||||
"Generics.md",
|
"Generics.md",
|
||||||
"Lifetimes.md",
|
"Lifetimes.md",
|
||||||
"2025-02-04.md",
|
"2025-02-04.md",
|
||||||
"data_types.md",
|
|
||||||
"does_not_compile.svg",
|
"does_not_compile.svg",
|
||||||
"Untitled.canvas",
|
"Untitled.canvas",
|
||||||
"Good and Bad Code/Commenting Pratices",
|
"Good and Bad Code/Commenting Pratices",
|
||||||
|
@ -46,4 +46,4 @@ Here are the topics that will be covered in this section:
|
|||||||
- How to create threads to run multiple pieces of code at the same time [Section Link Here](./Simultaneous%20Code%20Running.md)
|
- How to create threads to run multiple pieces of code at the same time [Section Link Here](./Simultaneous%20Code%20Running.md)
|
||||||
- *Message-passing* concurrency, where channels send messages between threads [Section Link Here](./Passing%20Data%20Between%20Threads.md)
|
- *Message-passing* concurrency, where channels send messages between threads [Section Link Here](./Passing%20Data%20Between%20Threads.md)
|
||||||
- *Shared-state* concurrency, where multiple threads have access to some piece of data [Section Link Here](./Shared%20State%20Concurrency.md)
|
- *Shared-state* concurrency, where multiple threads have access to some piece of data [Section Link Here](./Shared%20State%20Concurrency.md)
|
||||||
- The `Sync` and `Send` traits, which extend Rust's concurrency guarantees to use-defined types as well as types provided by the std library
|
- The `Sync` and `Send` traits, which extend Rust's concurrency guarantees to use-defined types as well as types provided by the std library [Section Link Here](./Sync%20and%20Send.md)
|
@ -70,3 +70,295 @@ fn main() {
|
|||||||
println!("m = {m:?}");
|
println!("m = {m:?}");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
Just like with many types, we create a `Mutex<T>` using the associated function `new`
|
||||||
|
|
||||||
|
To access the data in the data inside the mutex, we use the `lock` method to acquire the lock.
|
||||||
|
|
||||||
|
This call will block the current thread so it can't do any work until its 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.
|
||||||
|
|
||||||
|
Here we chose to `unwrap` and have this thread panic if we are in that situation.
|
||||||
|
|
||||||
|
After the acquire 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 the value in `m`.
|
||||||
|
|
||||||
|
The type of `m` is `Mutex<i32>`, not `i32`, therefore we *must* call `lock` to be able to use the `i32` value.
|
||||||
|
|
||||||
|
The type system won't let us access the inner `i32` otherwise.
|
||||||
|
|
||||||
|
`Mutex<T>` is a smart pointer.
|
||||||
|
|
||||||
|
More accurately, the call to `lock` *returns* a smart pointer called `MutexGuard`, wrapped in a `LockResult` that we handled with the call to `unwrap`.
|
||||||
|
|
||||||
|
The `MutexGaurd` smart pointer implements `Deref` to point at our inner data.
|
||||||
|
|
||||||
|
The smart pointer also has a `Drop` implementation that releases the lock automatically when a `MutexGaurd` goes out of scope. This happens at the end of the inner scope.
|
||||||
|
|
||||||
|
This results in not risking forgetting to release the lock and blocking the mutex form being used by other threads, because the lock releases happens automatically.
|
||||||
|
|
||||||
|
After dropping the lock, we can print the mutex value and see that we are able to change the inner `i32` to 6.
|
||||||
|
|
||||||
|
## Sharing a `Mutex<T>` Between Multiple Threads
|
||||||
|
Here we will try to share a value between multiple threads suing `Mutex<T>`.
|
||||||
|
|
||||||
|
We will spin up 10 threads and each will increment a counter by 1.
|
||||||
|
|
||||||
|
The counter will go from 0 to 10.
|
||||||
|
|
||||||
|
In this example it will give a compiler error and we will use that to learn a bit more about using `Mutex<T>` and how Rust helps us use it correctly.
|
||||||
|
```rust
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let counter = Mutex::new(0);
|
||||||
|
let mut handles = vec![];
|
||||||
|
|
||||||
|
for _ in 0..10 {
|
||||||
|
let handle = thread::spawn(move || {
|
||||||
|
let mut num = counter.lock().unwrap();
|
||||||
|
|
||||||
|
*num += 1;
|
||||||
|
});
|
||||||
|
handles.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
for handle in handles {
|
||||||
|
handle.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Result: {}", *counter.lock().unwrap());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Here we create the `counter` variable which holds an `i32` inside a `Mutex<T>`, just like before.
|
||||||
|
|
||||||
|
Then we create 10 threads by iterating over a range of numbers.
|
||||||
|
|
||||||
|
We use `thread::spawn` and give all the threads the same closure.
|
||||||
|
|
||||||
|
This will moves the counter into the thread, acquires a lock on the `Mutex<T>` by calling the `lock` method, then adding 1 to the value in the mutex.
|
||||||
|
|
||||||
|
Finally when a thread finishes running its closure, `num` will go out of scope and release the lock so another thread can acquire it.
|
||||||
|
|
||||||
|
Here in the main thread we collect all the join handles.
|
||||||
|
|
||||||
|
Then just as we did before we call `join` on each handle to make sure that all the threads finish.
|
||||||
|
|
||||||
|
Once all the threads have finished the main thread will acquire the lock and print the result of the program.
|
||||||
|
|
||||||
|
Here is the output and compiler error
|
||||||
|
```
|
||||||
|
$ cargo run
|
||||||
|
Compiling shared-state v0.1.0 (file:///projects/shared-state)
|
||||||
|
error[E0382]: borrow of moved value: `counter`
|
||||||
|
--> src/main.rs:21:29
|
||||||
|
|
|
||||||
|
5 | let counter = Mutex::new(0);
|
||||||
|
| ------- move occurs because `counter` has type `Mutex<i32>`, which does not implement the `Copy` trait
|
||||||
|
...
|
||||||
|
8 | for _ in 0..10 {
|
||||||
|
| -------------- inside of this loop
|
||||||
|
9 | let handle = thread::spawn(move || {
|
||||||
|
| ------- value moved into closure here, in previous iteration of loop
|
||||||
|
...
|
||||||
|
21 | println!("Result: {}", *counter.lock().unwrap());
|
||||||
|
| ^^^^^^^ value borrowed here after move
|
||||||
|
|
|
||||||
|
help: consider moving the expression out of the loop so it is only moved once
|
||||||
|
|
|
||||||
|
8 ~ let mut value = counter.lock();
|
||||||
|
9 ~ for _ in 0..10 {
|
||||||
|
10 | let handle = thread::spawn(move || {
|
||||||
|
11 ~ let mut num = value.unwrap();
|
||||||
|
|
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0382`.
|
||||||
|
error: could not compile `shared-state` (bin "shared-state") due to 1 previous error
|
||||||
|
```
|
||||||
|
This error message states that the `counter` value was moved in the pervious iteration of the loop.
|
||||||
|
|
||||||
|
Rust is telling us that we can't move the ownership of `counter` into multiple threads.
|
||||||
|
|
||||||
|
We will fix this compilation error with a multiple-ownership method discussed previously. [Ch15](./Smart%20Pointers.md)
|
||||||
|
|
||||||
|
### Multiple Ownership with Multiple Threads
|
||||||
|
Previously we gave gave a value multiple owners by using the smart pointer `Rc<T>` to create a reference counted value.
|
||||||
|
|
||||||
|
Lets see what happens when we do the same here to see what happens.
|
||||||
|
|
||||||
|
We will wrap the `Mutex<T>` in `Rc<T>` and clone the `Rc<T>` before moving ownership to the thread.
|
||||||
|
```rust
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let counter = Rc::new(Mutex::new(0));
|
||||||
|
let mut handles = vec![];
|
||||||
|
|
||||||
|
for _ in 0..10 {
|
||||||
|
let counter = Rc::clone(&counter);
|
||||||
|
let handle = thread::spawn(move || {
|
||||||
|
let mut num = counter.lock().unwrap();
|
||||||
|
|
||||||
|
*num += 1;
|
||||||
|
});
|
||||||
|
handles.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
for handle in handles {
|
||||||
|
handle.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Result: {}", *counter.lock().unwrap());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This once again will give us a compilation error, but this is a different compiler error.
|
||||||
|
```
|
||||||
|
$ cargo run
|
||||||
|
Compiling shared-state v0.1.0 (file:///projects/shared-state)
|
||||||
|
error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely
|
||||||
|
--> src/main.rs:11:36
|
||||||
|
|
|
||||||
|
11 | let handle = thread::spawn(move || {
|
||||||
|
| ------------- ^------
|
||||||
|
| | |
|
||||||
|
| ______________________|_____________within this `{closure@src/main.rs:11:36: 11:43}`
|
||||||
|
| | |
|
||||||
|
| | required by a bound introduced by this call
|
||||||
|
12 | | let mut num = counter.lock().unwrap();
|
||||||
|
13 | |
|
||||||
|
14 | | *num += 1;
|
||||||
|
15 | | });
|
||||||
|
| |_________^ `Rc<Mutex<i32>>` cannot be sent between threads safely
|
||||||
|
|
|
||||||
|
= help: within `{closure@src/main.rs:11:36: 11:43}`, the trait `Send` is not implemented for `Rc<Mutex<i32>>`, which is required by `{closure@src/main.rs:11:36: 11:43}: Send`
|
||||||
|
note: required because it's used within this closure
|
||||||
|
--> src/main.rs:11:36
|
||||||
|
|
|
||||||
|
11 | let handle = thread::spawn(move || {
|
||||||
|
| ^^^^^^^
|
||||||
|
note: required by a bound in `spawn`
|
||||||
|
--> file:///home/.rustup/toolchains/1.82/lib/rustlib/src/rust/library/std/src/thread/mod.rs:675:8
|
||||||
|
|
|
||||||
|
672 | pub fn spawn<F, T>(f: F) -> JoinHandle<T>
|
||||||
|
| ----- required by a bound in this function
|
||||||
|
...
|
||||||
|
675 | F: Send + 'static,
|
||||||
|
| ^^^^ required by this bound in `spawn`
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0277`.
|
||||||
|
error: could not compile `shared-state` (bin "shared-state") due to 1 previous error
|
||||||
|
```
|
||||||
|
Here is the important part to focus on: ``Rc<Mutex<i32>>` cannot be send between threads safely`.
|
||||||
|
|
||||||
|
The compiler also tells us the reason why: the trait `Send` is not implemented `Rc<Mutex<i32>>`
|
||||||
|
|
||||||
|
We will discuss `Send` in the next section.
|
||||||
|
|
||||||
|
For now: it's one of the traits that ensures the types we use with threads are meant for use in concurrent situations.
|
||||||
|
|
||||||
|
`Rc<T>` is not safe to share across threads.
|
||||||
|
|
||||||
|
When `Rc<T>` manages the reference count, it adds to the count for each call to `clone` and subtracts from the count when each clone is dropped.
|
||||||
|
|
||||||
|
This doesn't use any concurrency primitives to make sure that changes to the count can't be interrupted by another thread.
|
||||||
|
|
||||||
|
This could lead to wrong counts, this could cause subtle bugs that could in turn lead to memory leaks or a value being dropped before we are don't with it.
|
||||||
|
|
||||||
|
We need something that is exactly like `Rc<T>` but one that makes changes to the reference count in a thread-safe way.
|
||||||
|
|
||||||
|
### Atomic Reference Counting with `Arc<T>`
|
||||||
|
`Arc<T>` *is* a type like `Rc<T>` that is safe to use in concurrent situations.
|
||||||
|
|
||||||
|
The *a* stands for *atomic* meaning that it is an *atomically reference counted* type.
|
||||||
|
|
||||||
|
Atomics are an additional kind of concurrency primitive that we won't cover in detail here.
|
||||||
|
|
||||||
|
See the std library documentation for [`std::sync::atomic`](https://doc.rust-lang.org/std/sync/atomic/index.html) for more info.
|
||||||
|
|
||||||
|
At this point know that atomics work like primitive types but are safe to share across threads.
|
||||||
|
|
||||||
|
You may wonder why all primitives types aren't atomic and why std library types aren't implemented to use `Arc<T>` by default.
|
||||||
|
|
||||||
|
This is due the performance penalty that comes with being thread safe. You only want to pay when you really need to.
|
||||||
|
|
||||||
|
If you are just performing operations on values within a single thread, your code can run faster if it doesn't have to enforce the guarantees atomics provide.
|
||||||
|
|
||||||
|
We will update our example to use `Arc<T>`.
|
||||||
|
|
||||||
|
`Arc<T>` and `Rc<T>` have the same API.
|
||||||
|
|
||||||
|
To fix our program by changing the `use` line to call to `new` and the call to `clone`.
|
||||||
|
|
||||||
|
Here is the updated code
|
||||||
|
```rust
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let counter = Arc::new(Mutex::new(0));
|
||||||
|
let mut handles = vec![];
|
||||||
|
|
||||||
|
for _ in 0..10 {
|
||||||
|
let counter = Arc::clone(&counter);
|
||||||
|
let handle = thread::spawn(move || {
|
||||||
|
let mut num = counter.lock().unwrap();
|
||||||
|
|
||||||
|
*num += 1;
|
||||||
|
});
|
||||||
|
handles.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
for handle in handles {
|
||||||
|
handle.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Result: {}", *counter.lock().unwrap());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This will print the following
|
||||||
|
```
|
||||||
|
Result: 10
|
||||||
|
```
|
||||||
|
This counts from 0 to 10.
|
||||||
|
|
||||||
|
This doesn't seem very impressive but this taught us a lot about `Mutex<T>` and thread safety.
|
||||||
|
|
||||||
|
This could also be used in a program's structure to do more complex operations than just incrementing a counter.
|
||||||
|
|
||||||
|
Using this strategy, you can divide a calculation into independent parts, split those parts across threads and then use a `Mutex<T>` to have each thread update the final result with its part.
|
||||||
|
|
||||||
|
Note that if you are doing simple numerical operations, there are types simpler than `Mutex<T>` types provided by the [`std::sync::atomic module`](https://doc.rust-lang.org/std/sync/atomic/index.html)
|
||||||
|
|
||||||
|
These types provide safe concurrent, atomic access to primitives types.
|
||||||
|
|
||||||
|
Here we decided to use `Mutex<T>` with a primitive type for this example so we could show how `Mutex<T>` works.
|
||||||
|
|
||||||
|
## Similarities Between `RefCell<T>`/`Rc<T>` and `Mutex<T>`/`Arc<T>`
|
||||||
|
Notice that `counter` is immutable but we could get a mutable reference to the value inside it.
|
||||||
|
|
||||||
|
This means `Mutex<T>` provides interior mutability, just like how the `Cell` family does.
|
||||||
|
|
||||||
|
In the same way we use `RefCell<T>` in [Ch15](./Smart%20Pointers.md) to allow us to mutate contents inside an `Rc<T>`, we use `Mutex<T>` to mutate contents inside an `Arc<T>`.
|
||||||
|
|
||||||
|
Another thing to notice is that Rust can't protect you form all kinds of logic errors when use use `Mutex<T>`.
|
||||||
|
|
||||||
|
Recall that using `Rc<T>` came with the risk of creating reference cycles, where tow `Rc<T>` values refer to each other, thus causing a memory leak.
|
||||||
|
|
||||||
|
Similarly, `Mutex<T>` comes with the rusk of creating *deadlocks*.
|
||||||
|
|
||||||
|
These occur when an operation needs to lock two resources and two threads each acquired one of the locks, thus causing them to wait for each other forever.
|
||||||
|
|
||||||
|
You can research deadlock mitigation strategies for mutexes in any language and have a go at implementing them in Rust.
|
||||||
|
|
||||||
|
The std library API documentation for `Mutex<T>` and `MutexGuard` offer useful info.
|
||||||
|
|
||||||
|
Next we will talk about the `Send` and `Sync` traits and how we can use them with custom types.
|
||||||
|
|
||||||
|
Go [Here](./Sync%20and%20Send.md) for the next chapter
|
1
Sync and Send.md
Normal file
1
Sync and Send.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Extensible Concurrency with the `Sync` and `Send` Traits
|
Loading…
x
Reference in New Issue
Block a user