mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-08-01 07:40:54 -06:00
Compare commits
2 Commits
96dbd779e7
...
6e94e27e06
Author | SHA1 | Date | |
---|---|---|---|
6e94e27e06 | |||
084ac0ecff |
64
.obsidian/workspace.json
vendored
64
.obsidian/workspace.json
vendored
@ -35,6 +35,34 @@
|
||||
"title": "Concurrency"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "42d65c7d3f15198d",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Async, Await, Futures and Streams.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Async, Await, Futures and Streams"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"type": "leaf",
|
||||
@ -63,34 +91,6 @@
|
||||
"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",
|
||||
"type": "leaf",
|
||||
@ -245,12 +245,14 @@
|
||||
"command-palette:Open command palette": false
|
||||
}
|
||||
},
|
||||
"active": "3d0ca0b1691c4c2f",
|
||||
"active": "42d65c7d3f15198d",
|
||||
"lastOpenFiles": [
|
||||
"Concurrency.md",
|
||||
"Async, Await, Futures and Streams.md",
|
||||
"Shared State Concurrency.md",
|
||||
"Smart Pointers.md",
|
||||
"Sync and Send.md",
|
||||
"().md",
|
||||
"Smart Pointers.md",
|
||||
"Simultaneous Code Running.md",
|
||||
"Passing Data Between Threads.md",
|
||||
"Leaky Reference Cycles.md",
|
||||
@ -272,8 +274,6 @@
|
||||
"Generic Types Traits and Lifetimes.md",
|
||||
"Generics.md",
|
||||
"Lifetimes.md",
|
||||
"2025-02-04.md",
|
||||
"data_types.md",
|
||||
"does_not_compile.svg",
|
||||
"Untitled.canvas",
|
||||
"Good and Bad Code/Commenting Pratices",
|
||||
|
1
Async, Await, Futures and Streams.md
Normal file
1
Async, Await, Futures and Streams.md
Normal file
@ -0,0 +1 @@
|
||||
# Fundamentals of Asynchronous Programming: Async, Await, Futures, and Streams
|
@ -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)
|
||||
- *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)
|
||||
- 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:?}");
|
||||
}
|
||||
```
|
||||
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
|
57
Sync and Send.md
Normal file
57
Sync and Send.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Extensible Concurrency with the `Sync` and `Send` Traits
|
||||
The Rust language gas *very* few concurrency features.
|
||||
|
||||
Almost every feature we talked about so far has been part of the std library and not the language.
|
||||
|
||||
Your options for handling concurrency are not limited to the language or the std library; you are able to write your own concurrency features or use those written by others.
|
||||
|
||||
However, tow concurrency concepts that are embedded into the language are the `std::marker` traits `Sync` and `Send`.
|
||||
|
||||
## Allowing Transference of Ownership Between Threads with `Send`
|
||||
The marker trait `Send` indicates that ownership of values of the type implemented `Send` can be transferred between threads.
|
||||
|
||||
Almost every Rust type is `Send`.
|
||||
|
||||
There are some exceptions, including `Rc<T>`.
|
||||
|
||||
This cannot be be `Send` because if you cloned a `Rc<T>` value and tried to transfer ownership of the clone to another thread, both threads might update the reference count at the same time.
|
||||
|
||||
This is the reason, `Rc<T>` is implemented for use in single-threaded situations where you don't want to pay the thread-safe performance penalty.
|
||||
|
||||
Rust's type system and trait bounds ensure that you can never accidentally send a `Rc<T>` value across threads unsafely.
|
||||
|
||||
When we tried to do this before we got the error: the trait `Send` is not implemented for `Rc<Mutex<i32>>`.
|
||||
|
||||
Any type composed entirely of `Send` types is automatically marked as `Send` as well.
|
||||
|
||||
Almost all primitives are `Send`, aside form raw pointers (This will be discussed in ch20).
|
||||
|
||||
## Allowing access form Multiple Threads with `Sync`
|
||||
|
||||
The `Sync` marker trait indicates that it is safe for the type implementing `Sync` to be referenced from multiple threads.
|
||||
|
||||
Any type `T` is `Sync` if `&T` (an immutable reference to `T`) is `Send`, meaning the reference can be sent safely to another thread.
|
||||
|
||||
Similar to `Send`, primitive types are `Sync` and types composed entirely of types that are `Sync` are also `Sync`.
|
||||
|
||||
The smart pointer `Rc<T>` is not `Sync` as well for the same reasons that it is not `Send`.
|
||||
|
||||
The `RefCell<T>` types ([Ch15](./Smart%20Pointers.md)) and the family related to `Cell<T>` types are not `Sync`.
|
||||
|
||||
The implementation for borrow checking that `RefCell<T>` does at runtime is not thread-safe.
|
||||
|
||||
|
||||
The smart pointer `Mutex<T>` is `Sync` and can be used to share access with multiple threads alike what we saw in ["Sharing a `Mutex<T>` Between Multiple Threads"](./Shared%20State%20Concurrency.md) Section.
|
||||
|
||||
## Implementing `Send` and `Sync` Manually is Unsafe
|
||||
Because types that are made up of `Send` and `Sync` traits are also automatically `Send` and `Sync`, we don't need to implement these traits manually.
|
||||
|
||||
As marker traits they don't even have any methods to implement.
|
||||
|
||||
These are just useful for enforcing invariants related to concurrency.
|
||||
|
||||
Manually implemented these traits involves implementing unsafe Rust code (This will be discussed in Ch20).
|
||||
|
||||
For now the important information is that building new concurrent types not made up of `Send` and `Sync` parts requires careful thought to uphold the safety guarantees.
|
||||
|
||||
["The Rustonomicion"](https://doc.rust-lang.org/nomicon/index.html) has more information about these guarantees and how to uphold them.
|
Reference in New Issue
Block a user