2.0 KiB
Scoped threads
All the lifetime issues we discussed so far have a common source: the
spawned thread can outlive its parent.
We can sidestep this issue by using scoped threads.
let v = vec![1, 2, 3];
let midpoint = v.len() / 2;
std::thread::scope(|scope| {
scope.spawn(|| {
let first = &v[..midpoint];
println!("Here's the first half of v: {first:?}");
});
scope.spawn(|| {
let second = &v[midpoint..];
println!("Here's the second half of v: {second:?}");
});
});
println!("Here's v: {v:?}");Let’s unpack what’s happening.
scope
The std::thread::scope function creates a new
scope.
std::thread::scope takes a closure as input, with a single
argument: a Scope instance.
Scoped spawns
Scope exposes a spawn method.
Unlike std::thread::spawn, all threads spawned using a
Scope will be automatically joined when
the scope ends.
If we were to “translate” the previous example to
std::thread::spawn, it’d look like this:
let v = vec![1, 2, 3];
let midpoint = v.len() / 2;
let handle1 = std::thread::spawn(|| {
let first = &v[..midpoint];
println!("Here's the first half of v: {first:?}");
});
let handle2 = std::thread::spawn(|| {
let second = &v[midpoint..];
println!("Here's the second half of v: {second:?}");
});
handle1.join().unwrap();
handle2.join().unwrap();
println!("Here's v: {v:?}");Borrowing from the environment
The translated example wouldn’t compile, though: the compiler would
complain that &v can’t be used from our spawned threads
since its lifetime isn’t 'static.
That’s not an issue with std::thread::scope—you can
safely borrow from the environment.
In our example, v is created before the spawning points.
It will only be dropped after scope returns. At
the same time, all threads spawned inside scope are
guaranteed to finish before scope returns,
therefore there is no risk of having dangling references.
The compiler won’t complain!