multithreadingrust

How to share a boolean flag with a scoped thread in Rust?


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).

This is not permitted because of the borrow checker rules.

btw got the idea of doing this from this thread


Solution

  • 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.