finished ch16.3

This commit is contained in:
darkicewolf50 2025-03-17 15:05:12 -06:00
parent 96dbd779e7
commit 084ac0ecff
5 changed files with 311 additions and 32 deletions

0
().md
View File

View File

@ -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",

View File

@ -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)

View File

@ -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
View File

@ -0,0 +1 @@
# Extensible Concurrency with the `Sync` and `Send` Traits