I am trying to use a boolean flag to signal that a thread should exit. However, currently my code violates the borrow checker rules. I understand why it violates these rules, however what I do not know is what the most appropriate design is and how that may fix the problem.
fn long_running(exit_flag: &bool) {
loop {
std::thread::sleep(std::time::Duration::from_secs(10));
if *exit_flag {
break;
}
}
}
fn main() {
let mut exit_flag = false;
std::thread::scope(
|scope| {
let handle = scope.spawn(
|| {
long_running(&exit_flag); # (1)
}
);
exit_flag = true; # (2)
// terminate the consumer poll loop
handle.join().expect("failed to join thread");
}
);
}
The problem involves the lines marked with # (1)
and # (2)
.
# (1)
: Reference to exit_flag
is borrowed# (2)
: Attempting to mutate underlying variable while reference is still borrowedThis is not permitted because of the borrow checker rules.
std::thread::scope
was to permit launching a thread which has access to a local variable from the stack of the parent (main) thread.btw got the idea of doing this from this thread
This does not work because exit_flag
is to be borrowed by the thread's closure while also being mutated via exit_flag = true;
. In other words, there is no guarantee that the thread has stopped observing exit_flag
while it is being mutated (which in fact is the whole point of exit_flag
).
You'll need an AtomicBool
to do that:
use std::sync::atomic::{AtomicBool, Ordering};
fn long_running(exit_flag: &AtomicBool) {
loop {
println!("Working...");
std::thread::sleep(std::time::Duration::from_secs(3));
if exit_flag.load(Ordering::Relaxed) {
break;
}
}
}
fn main() {
let exit_flag = AtomicBool::new(false);
std::thread::scope(|scope| {
scope.spawn(|| {
long_running(&exit_flag);
});
exit_flag.store(true, Ordering::Relaxed);
});
println!("Done");
}
Notice that I removed handle.join()
, as thread::scope
already guarantees that all threads have been joined when it returns.