diff --git a/Passing Data Between Threads.md b/Passing Data Between Threads.md new file mode 100644 index 0000000..bc5143c --- /dev/null +++ b/Passing Data Between Threads.md @@ -0,0 +1 @@ +# Using Message Passing to Transfer Data Between Threads diff --git a/Simultaneous Code Running.md b/Simultaneous Code Running.md index 6614616..004b671 100644 --- a/Simultaneous Code Running.md +++ b/Simultaneous Code Running.md @@ -1 +1,340 @@ -# Using Threads to Run Code Simultaneously \ No newline at end of file +# Using Threads to Run Code Simultaneously +In most modern operating systems, an executed program's program's code is run in a *process*, and the operating system will manage multiple process at once. + +Within a progam you can also have independent components that run simultaneously. + +The features that run these independent parts are called *threads*. + +An example of this is a we server could have multiple threads so that it could respond to more than one request at the same time. + +Splitting this computation in yor program into multiple threads to run multiple tasks at the same time can improve performance. + +This comes with the drawback of added complexity. + +Due to threads being able to run simultaneously, there is no ingerent guarantee about the order in which parts of your code on different threads will run. + +This can lead to problems like: +- Race conditions - where threads are accessing data or resources in an inconsistent order +- Deadlock - where two threads are waiting for each other, preventing both threads from continuing +- Bugs that happen only in specific cases and are hard to reproduce and fix reliably + +Rust attempts to minimize the negative effects of using threads, but programming in a mutlithreaded context still takes a lot of careful thought and reuires a code structure that is different from that in programs running in a single threaded manner. + +Rrogramming languages often implement threads in a few different ways, and amny operating systmes provide an API the language can call for creating new threads + +The Rust std library uses a *1:1* model of thread implementation, whereby a program uses one operating system thread per on language thread. + +There are crates that implement other models of threading that make different tradeoffs to the 1:1 model + +(Rust's aync system provides another approach to concurrency as well, we will see this in th nex section.) + +## Creating a New Thread with `spawn` +To create a new thread, we use the `thread::spawn` function and pass it a closure (this was discussed previously [here](./Closures.md)). + +This contains the code we want to run in the new thread. + +Here is an example where this code prints some text from a main thread and other text from a new thread +```rust +use std::thread; +use std::time::Duration; + +fn main() { + thread::spawn(|| { + for i in 1..10 { + println!("hi number {i} from the spawned thread!"); + thread::sleep(Duration::from_millis(1)); + } + }); + + for i in 1..5 { + println!("hi number {i} from the main thread!"); + thread::sleep(Duration::from_millis(1)); + } +} +``` + +Note that when the main thread of Rust program completes, all spawned threads are shut down, whether or not they have finished running. + +The output form this program might be a little different every time. + +Overall it will look similar to this output +``` +hi number 1 from the main thread! +hi number 1 from the spawned thread! +hi number 2 from the main thread! +hi number 2 from the spawned thread! +hi number 3 from the main thread! +hi number 3 from the spawned thread! +hi number 4 from the main thread! +hi number 4 from the spawned thread! +hi number 5 from the spawned thread! +``` +The call to `thread::sleep` force a thread to stop its execution for a short duration. + +This allws for a different thread to run. + +The threads will probably take turns, this is not guaranteed. + +This is entirely dependant on how your operating system schedules the threads. + +Here in this run, the main thread is printed first, even though the print statement from the spawned thread appears first. + +And even though we told the spawned thread to print unti `i` is 9, it only got to 5 before the main thread shut down. + +If you run this code and only see the output form the main thread, or dont see any overlap. You can try increasing the numbers in the ranges to create mroe opportunities for the operating system to switch between the threads. + +## Waiting for All Threads to Finishe Using `join` Handles +THe code from the example before not only stops the spwaned thread prematurely most of the time due to the main thread ending. + +There is no guarantee on the order in which the threads run, we also can't guarantee that the spawned thread will get to run at all. + +We can fix the problem of the spawned thread not running o ending prematurely by saving value of `thread::spawn` in a variable. + +The return type of `thread::spawn` is `JoinHandle`. + +A `JoinHandle` is an owned value that, wqhen we call the `join` method on it, will wait for its thread to finish. + +This next example shows how to use the `JoinHandle` of the thread we created in and call `join` to make sure the spawned thread finishes before `main` exits: +```rust +use std::thread; +use std::time::Duration; + +fn main() { + let handle = thread::spawn(|| { + for i in 1..10 { + println!("hi number {i} from the spawned thread!"); + thread::sleep(Duration::from_millis(1)); + } + }); + + for i in 1..5 { + println!("hi number {i} from the main thread!"); + thread::sleep(Duration::from_millis(1)); + } + + handle.join().unwrap(); +} +``` +Here calling `join` on the handle blocks the thread currently running until the thread represented by the handle terminates. + +*Blocking* a thread means that the thread is prevented form performing work or exiting. + +Due to us putting the `join` call after the main thread's `for` loop. + +Running this now the output should look similar to this +``` +hi number 1 from the main thread! +hi number 2 from the main thread! +hi number 1 from the spawned thread! +hi number 3 from the main thread! +hi number 2 from the spawned thread! +hi number 4 from the main thread! +hi number 3 from the spawned thread! +hi number 4 from the spawned thread! +hi number 5 from the spawned thread! +hi number 6 from the spawned thread! +hi number 7 from the spawned thread! +hi number 8 from the spawned thread! +hi number 9 from the spawned thread! +``` +The two threads will alternate btween the two, but the main thread waits because of the call to `handle.join()`. + +It does not end until the spawned thread is finished. + +Lets see what happens when we move `handle.join()` to before the `for` loop in main +```rust +use std::thread; +use std::time::Duration; + +fn main() { + let handle = thread::spawn(|| { + for i in 1..10 { + println!("hi number {i} from the spawned thread!"); + thread::sleep(Duration::from_millis(1)); + } + }); + + handle.join().unwrap(); + + for i in 1..5 { + println!("hi number {i} from the main thread!"); + thread::sleep(Duration::from_millis(1)); + } +} +``` +Now the main thread will wait for the spawned thread to finish and then run its `for` loop. + +Now the output will not be interleaved anymore, like this +``` +hi number 1 from the spawned thread! +hi number 2 from the spawned thread! +hi number 3 from the spawned thread! +hi number 4 from the spawned thread! +hi number 5 from the spawned thread! +hi number 6 from the spawned thread! +hi number 7 from the spawned thread! +hi number 8 from the spawned thread! +hi number 9 from the spawned thread! +hi number 1 from the main thread! +hi number 2 from the main thread! +hi number 3 from the main thread! +hi number 4 from the main thread! +``` +These small details, such as where `join` is called, can affect whether or not your threads run at the same time. + +We need `.unwrap` to consume the closures otherwise it will not run. + +## Using `move` Closures with Threads +We often use the `move` keyword with closures passed to `thread::spawn`. + +This is due the closure taking ownership of the values it uses from the environment, and ths transferring ownership of those values form one thread to another. + +Remember the [Capturing References or Moving Ownership](./Closures.md#capturing-references-or-moving-ownership) section, where we discussed `move` in the context of clousres. + +Here we will focus on the interaction between `move` and `thread::spawn`. + +Note that in the first example that the closure we pass to `thread::spawn` takes no args. + +There we are not using any data from the main thread in the spawned thread's code. + +To use data from the main thread within the spawned thread, we need the spawned thread's closure to capture the values it needs. + +In this example shows an attempt to create a vector in the main thread a nd use it in the spawned thread. + +This will not comile, it will be exampled afterwards. +```rust +use std::thread; + +fn main() { + let v = vec![1, 2, 3]; + + let handle = thread::spawn(|| { + println!("Here's a vector: {v:?}"); + }); + + handle.join().unwrap(); +} +``` +This closures uses `v`, so it will capture `v` and make it part of the closures environment. + +Due to `thread::spawn` runs this closure in a new thread. + +Here we should be able to access `v` inside that new thread. + +But when we compile this we get this compiler error +``` +$ cargo run + Compiling threads v0.1.0 (file:///projects/threads) +error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function + --> src/main.rs:6:32 + | +6 | let handle = thread::spawn(|| { + | ^^ may outlive borrowed value `v` +7 | println!("Here's a vector: {v:?}"); + | - `v` is borrowed here + | +note: function requires argument type to outlive `'static` + --> src/main.rs:6:18 + | +6 | let handle = thread::spawn(|| { + | __________________^ +7 | | println!("Here's a vector: {v:?}"); +8 | | }); + | |______^ +help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword + | +6 | let handle = thread::spawn(move || { + | ++++ + +For more information about this error, try `rustc --explain E0373`. +error: could not compile `threads` (bin "threads") due to 1 previous error +``` +Rust here will *infer* how to capture `v` and because `println!` only needs a reference to `v`, the closure tires to borrow `v`. + +Here the problem is that Rust can't tell how long the spawned thread will run so it doesn't know if the reference to `v` will always be valid. + +This next example provides a scenario that is more likely to habve a referncfe to `v` that will not be valid +```rust +use std::thread; + +fn main() { + let v = vec![1, 2, 3]; + + let handle = thread::spawn(|| { + println!("Here's a vector: {v:?}"); + }); + + drop(v); // oh no! + + handle.join().unwrap(); +} +``` +If Rust allwed this code to run, there is a possibility that the spawned thread would be immediately put in the backgrounf without running at all. + +The spawned reference to `v` inside, but the main thread immediately drops `v` usng the `drop` function. + +Then when the spawned thread starts to execute, `v` is no longer valid, so a reference to it is also invalid. + +Here is what the compiler suggests to fix this, it is located in the compiler error message +``` +help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword + | +6 | let handle = thread::spawn(move || { + | ++++ +``` +By adding the `move` keyword before the closure. This forces the closure to take ownership of the values it is using rather than allowing Rust to unfer that it should borrow the values. + +Here is the modification to the bfore exampl that will compile and run as intended +```rust +use std::thread; + +fn main() { + let v = vec![1, 2, 3]; + + let handle = thread::spawn(move || { + println!("Here's a vector: {v:?}"); + }); + + handle.join().unwrap(); +} +``` +We may be tempted to try the sam ething to dix the code in the previous example where the main thread called `drop` by using a `move` closure. + +This fix will not work because od what the example is attempting to do is disallowed for a different reason. + +If we were to add `move` to the closure, we would move `v` into the closure's environment and we could no longer call `drop` on it in the main thread. + +We would get this compiler error instead +``` +$ cargo run + Compiling threads v0.1.0 (file:///projects/threads) +error[E0382]: use of moved value: `v` + --> src/main.rs:10:10 + | +4 | let v = vec![1, 2, 3]; + | - move occurs because `v` has type `Vec`, which does not implement the `Copy` trait +5 | +6 | let handle = thread::spawn(move || { + | ------- value moved into closure here +7 | println!("Here's a vector: {v:?}"); + | - variable moved due to use in closure +... +10 | drop(v); // oh no! + | ^ value used here after move + +For more information about this error, try `rustc --explain E0382`. +error: could not compile `threads` (bin "threads") due to 1 previous error +``` +The Rust ownership rules save us again. + +The we get this error becuase Rust was being conservative and only borrowing `v` for the thread. + +This means that the thread could theoretically invaildate, we are guaranteeing Rust that the main thread won't use `v` anymore. + +If we change the example form before in the same way then we would voilate the ownership rules rules when we try to use `v` in the main thread. + +The `move` keyword overrides Rust's conservative default of borrowing; it doesnt let us violate the ownership rules. + +Now with a basic understanding of threads and the thread API, we will look at what we can *do* with threads + +[Next Section Here]() \ No newline at end of file