-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #68 from ferrous-systems/spawning-threads
Add threads and closures.
- Loading branch information
Showing
2 changed files
with
334 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,153 @@ | ||
# Closures | ||
|
||
## Rust's Function Traits | ||
|
||
* `trait FnOnce<Args>` | ||
* `trait FnMut<Args>: FnOnce<Args>` | ||
* `trait Fn<Args>: FnMut<Args>` | ||
|
||
Note: | ||
|
||
* Instances of FnOnce can only be called once. | ||
* Instances of FnMut can be called repeatedly and may mutate state. | ||
* Instances of Fn can be called repeatedly without mutating state. | ||
* `Fn` (a trait) and `fn` (a function pointer) are different! | ||
|
||
## These traits are implemented by: | ||
|
||
* Function Pointers | ||
* Closures | ||
|
||
## Function Pointers | ||
|
||
```rust | ||
fn add_one(x: usize) -> usize { | ||
x + 1 | ||
} | ||
|
||
fn main() { | ||
let ptr: fn(usize) -> usize = add_one; | ||
println!("ptr(5) = {}", ptr(5)); | ||
} | ||
``` | ||
|
||
## Closures | ||
|
||
* Defined with `|<args>|` | ||
* Most basic kind, are just function pointers | ||
|
||
```rust | ||
fn main() { | ||
let clos: fn(usize) -> usize = |x| x + 5; | ||
println!("clos(5) = {}", clos(5)); | ||
} | ||
``` | ||
|
||
## Capturing | ||
|
||
* Closures can capture their environment. | ||
* Now it's an anonymous `struct`, not a `fn` | ||
* It implements `Fn` | ||
|
||
```rust | ||
fn main() { | ||
let increase_by = 1; | ||
let clos = |x| x + increase_by; | ||
println!("clos(5) = {}", clos(5)); | ||
} | ||
``` | ||
|
||
## Capturing Mutably | ||
|
||
* Closures can capture their environment by mutable reference | ||
* Now it implements `FnMut` | ||
|
||
```rust | ||
fn main() { | ||
let mut total = 0; | ||
let mut update = |x| total += x; | ||
update(5); | ||
update(5); | ||
println!("total: {}", total); | ||
} | ||
``` | ||
|
||
Note: | ||
|
||
The closure is dropped before the `println!`, making `total` accessible again (the &mut ref stored in the closure is now gone). | ||
If you try and call `update()` after the `println!` you get a compile error. | ||
|
||
## Capturing by transferring ownership | ||
|
||
This closure implements `FnOnce`. | ||
|
||
```rust | ||
fn main() { | ||
let items = vec![1, 2, 3, 4]; | ||
let update = move || { | ||
for item in items { | ||
println!("item is {}", item); | ||
} | ||
}; | ||
update(); | ||
// println!("items is {:?}", items); | ||
} | ||
``` | ||
|
||
## But why? | ||
|
||
* But why is this useful? | ||
* It makes iterators really powerful! | ||
|
||
```rust [] | ||
fn main() { | ||
let items = [1, 2, 3, 4, 5, 6]; | ||
let n = 2; | ||
for even_number in items.iter().filter(|x| (**x % n) == 0) { | ||
println!("{} is even", even_number); | ||
} | ||
} | ||
``` | ||
|
||
## Cleaning up | ||
|
||
It's also very powerful if you have something you need to clean up. | ||
|
||
1. You do some set-up | ||
2. You want do some work (defined by the caller) | ||
3. You want to clean up after. | ||
|
||
```rust [] | ||
fn setup_teardown<F, T>(f: F) -> T where F: FnOnce(&mut Vec<u32>) -> T { | ||
let mut state = Vec::new(); | ||
println!("> Setting up state"); | ||
let t = f(&mut state); | ||
println!("< State contains {:?}", state); | ||
t | ||
} | ||
``` | ||
|
||
## Cleaning up | ||
|
||
```rust [] | ||
fn setup_teardown<F, T>(f: F) -> T where F: FnOnce(&mut Vec<u32>) -> T { | ||
let mut state = Vec::new(); | ||
println!("> Setting up state"); | ||
let t = f(&mut state); | ||
println!("< State contains {:?}", state); | ||
t | ||
} | ||
|
||
fn main() { | ||
setup_teardown(|s| s.push(1)); | ||
setup_teardown(|s| { | ||
s.push(1); | ||
s.push(2); | ||
s.push(3); | ||
}); | ||
} | ||
``` | ||
|
||
Note: | ||
|
||
In release mode, all this code just gets inlined. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,183 @@ | ||
# Spawning Threads and Scoped Threads | ||
|
||
## Platform Differences - Windows | ||
|
||
* On Windows, a *Process* is just an address space, and it has one *Thread* by default. | ||
* You can start more *Threads* | ||
|
||
```c | ||
HANDLE CreateThread( | ||
/* [in, optional] */ LPSECURITY_ATTRIBUTES lpThreadAttributes, | ||
/* [in] */ SIZE_T dwStackSize, | ||
/* [in] */ LPTHREAD_START_ROUTINE lpStartAddress, // <<-- function to run in thread | ||
/* [in, optional] */ __drv_aliasesMem LPVOID lpParameter, // <<-- context for thread function | ||
/* [in] */ DWORD dwCreationFlags, | ||
/* [out, optional] */ LPDWORD lpThreadId | ||
); | ||
``` | ||
|
||
## Platform Differences - POSIX | ||
|
||
* On POSIX, a *Process* includes one thread of execution. | ||
* You can start more *Threads*, typically using the POSIX Threads API | ||
|
||
```c | ||
int pthread_create( | ||
pthread_t *restrict thread, | ||
const pthread_attr_t *restrict attr, | ||
void *(*start_routine)(void *), // <<-- function to run in thread | ||
void *restrict arg // <<-- context for thread function | ||
); | ||
``` | ||
|
||
## Rusty Threads | ||
|
||
The Rust [thread API](https://doc.rust-lang.org/std/thread/) looks like this: | ||
|
||
```rust ignore | ||
pub fn spawn<F, T>(f: F) -> JoinHandle<T> | ||
where | ||
F: FnOnce() -> T + Send + 'static, | ||
T: Send + 'static, | ||
``` | ||
|
||
## Using spawn | ||
|
||
* You *could* pass a function to `std::thread::spawn`. | ||
* In almost all cases you pass a *closure* | ||
|
||
```rust [] | ||
use std::{thread, time}; | ||
|
||
fn main() { | ||
let thread_handle = thread::spawn(|| { | ||
thread::sleep(time::Duration::from_secs(1)); | ||
println!("I'm a thread"); | ||
}); | ||
|
||
thread_handle.join().unwrap(); | ||
} | ||
``` | ||
|
||
## Why no context? | ||
|
||
There's no `void* p_context` argument, because *closures* can *close-over* local variables. | ||
|
||
```rust [] | ||
use std::thread; | ||
|
||
fn main() { | ||
let number_of_loops = 5; // on main's stack | ||
let thread_handle = thread::spawn(move || { | ||
for _i in 0..number_of_loops { // captured by value, not reference | ||
println!("I'm a thread"); | ||
} | ||
}); | ||
|
||
thread_handle.join().unwrap(); | ||
} | ||
``` | ||
|
||
Note: | ||
|
||
Try changing this *move* closure to a regular referencing closure. | ||
|
||
## Context lifetimes | ||
|
||
However, the thread might live forever... | ||
|
||
```rust [] | ||
use std::{sync::Mutex, thread}; | ||
|
||
fn main() { | ||
let buffer: Mutex<Vec<i32>> = Mutex::new(Vec::new()); | ||
let thread_handle = thread::spawn(|| { | ||
for i in 0..5 { | ||
// captured by reference, does not live long enough | ||
// buffer.lock().unwrap().push(i); | ||
} | ||
}); | ||
thread_handle.join().unwrap(); | ||
let locked_buffer = buffer.lock(); | ||
println!("{:?}", &locked_buffer); | ||
} | ||
|
||
``` | ||
|
||
## Making context live forever | ||
|
||
If a thread can live forever, we need its context to live just as long. | ||
|
||
```rust [] | ||
use std::{sync::{Arc, Mutex}, thread}; | ||
|
||
fn main() { | ||
let buffer = Arc::new(Mutex::new(Vec::new())); | ||
let thread_buffer = buffer.clone(); | ||
let thread_handle = thread::spawn(move || { | ||
for i in 0..5 { | ||
thread_buffer.lock().unwrap().push(i); | ||
} | ||
}); | ||
thread_handle.join().unwrap(); | ||
let locked_buffer = buffer.lock().unwrap(); | ||
println!("{:?}", &locked_buffer); | ||
} | ||
``` | ||
|
||
## Tidying up the handle | ||
|
||
* In Rust, functions take *expressions* | ||
* Blocks are expressions... | ||
|
||
```rust ignore | ||
let thread_buffer = buffer.clone(); | ||
let thread_handle = thread::spawn( | ||
move || { | ||
for i in 0..5 { | ||
thread_buffer.lock().unwrap().push(i); | ||
} | ||
} | ||
); | ||
``` | ||
|
||
## Tidying up the handle | ||
|
||
* In Rust, functions take *expressions* | ||
* Blocks are expressions... | ||
|
||
```rust ignore | ||
let thread_handle = thread::spawn({ | ||
let thread_buffer = buffer.clone(); | ||
move || { | ||
for i in 0..5 { | ||
thread_buffer.lock().unwrap().push(i); | ||
} | ||
} | ||
}); | ||
``` | ||
|
||
Note: | ||
|
||
This clearly limits the visual scope of the `thread_buffer` variable, to match the logical scope caused by the fact it is transferred by value into the closure. | ||
|
||
## Scoped Threads | ||
|
||
As of 1.63, we can say the threads will all have ended before we carry on our calling function. | ||
|
||
```rust [] | ||
use std::{sync::Mutex, thread}; | ||
|
||
fn main() { | ||
let buffer = Mutex::new(Vec::new()); | ||
thread::scope(|s| { | ||
s.spawn(|| { | ||
for i in 0..5 { | ||
buffer.lock().unwrap().push(i); | ||
} | ||
}); | ||
}); | ||
let locked_buffer = buffer.lock().unwrap(); | ||
println!("{:?}", &locked_buffer); | ||
} | ||
``` |