Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I share access to an AtomicBool between threads?

I have this little program - essentially I want one thread to be able to tell the other to stop via a shared boolean in a struct.

use std::thread;
use std::thread::JoinHandle;
use std::time::Duration;
use std::sync::atomic::{AtomicBool, Ordering};

struct Test {
    should_stop: AtomicBool,
    running_thread_handles: Vec<JoinHandle<()>>
}

impl Test {
    fn new() -> Test {
        Test { 
            should_stop: AtomicBool::new(false), 
            running_thread_handles: Vec::new() 
        }
    }

    fn stop(&mut self) {
        self.should_stop.store(true, Ordering::Relaxed);
    }

    fn start(&mut self) {
        let handle = thread::spawn(move || {
            loop {
                println!("Looping");
                thread::sleep(Duration::from_millis(500));

                // I want to effectively do this...
                /*
                if self.stop_bool.load(Ordering::Relaxed) {
                    println!("Stopping");
                    break;
                }*/
            }
        });

        self.running_thread_handles.push(handle);
    }
}

impl Drop for Test {
    fn drop(&mut self) {
        self.stop();

        // Here I want to iterate the self.running_thread_handles and
        // make sure they are cleaned up
    }
}

// I expect to see a 4 "Looping" messages and then a "Stopping" message
fn main() {
   let mut test = Test::new();
   test.start();
   thread::sleep(Duration::from_millis(2000));
   test.stop();
}

Perhaps there is a better approach altogether but I figure this is probably a good way to understand the lifetime stuff a bit.

I thought that all I need is an Arc so I tried this:

fn start(&mut self) {
    let stop_bool = Arc::new(&self.should_stop).clone();

    let handle = thread::spawn(move || {
        loop {
            println!("Looping");
            thread::sleep(Duration::from_millis(500));

            if stop_bool.load(Ordering::Relaxed) {
                println!("Stopping");
                break;
            }
        }
    });

    self.running_thread_handles.push(handle);
}

That gives me this error:

error: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements

I figure the compiler does not understand the lifetime of the threads since all I am doing is storing the handles in a vector so I need to tell it somehow, but how?

If I declare the struct like this would I be getting nearer to making it work?

struct Test<'a> {
    should_stop: AtomicBool,
    running_thread_handles: &'a Vec<JoinHandle<()>>
}

I also have secondary, related, troubles that I cannot for the life of me figure out how to iterate through my vector of handles and call any functions on them in the Drop impl. I suppose that the solution to that would be related, so I will not ask that.

like image 367
kmp Avatar asked Nov 25 '15 08:11

kmp


1 Answers

There are two ways to access variables between threads:

  • borrowing, which requires guaranteeing that the variable's lifetime exceeds the threads' lifetime
  • shared ownership (via Arc):

Borrowing is not supported by the Standard Library at the moment, although 3rd-party crates such as crossbeam offer it. For shared ownership, Arc is indeed a possibility...

... however you need to consider what you put in Arc carefully:

let stop_bool = Arc::new(&self.should_stop).clone();

Here, you are creating a Arc<&'a AtomicBool> from a &'a self, and thus you are sharing ownership over a borrowed reference. I'll point you back to the above explanation: cross-thread borrowing is not supported in the Standard Library yet.

You need a Arc<AtomicBool> for proper shared ownership, and this is done by changing Test:

struct Test {
    should_stop: Arc<AtomicBool>,
    running_thread_handles: Vec<JoinHandle<()>>
}

Then, cloning it is easy:

let stop_bool = self.should_stop.clone();
like image 173
Matthieu M. Avatar answered Oct 28 '22 07:10

Matthieu M.