Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design help: threading within a struct

I'm new to Rust. As a learning exercise I'm trying to write a simple timer struct that I once wrote in C++. The interface and implementation looks something like this:

pub struct Timer {
    handle: Option<std::thread::JoinHandle<()>>,
    alive: bool,
}

impl Timer {
    pub fn new() {
        Timer {
            handle: None,
            alive: false,
        }
    }

    pub fn start(&'static mut self) {
        // Oops! How do I do this?
        self.handle = Some(std::thread::spawn(move || {
            self.alive = true;
            self.loop()
        }));
    }

    pub fn stop(&mut self) {
        self.alive = false;
        self.handle.unwrap().join()
    }

    pub fn loop(&self) {
        // while alive
    }
}

I understand why this is an error because of use of moved value: self within the start function, but I'm wondering how I'm supposed to design my struct so that something like this would work. In every scenario I can think of, I'll always have a double borrow situation.

I have a hunch that I need to learn more about interior mutability, but figured I would ask for design guidance before going down any more rabbit holes.

like image 245
sholsapp Avatar asked Feb 04 '17 18:02

sholsapp


People also ask

Are structs thread-safe?

Structs are thread-safe Conversely, reference types can be accessed from virtually any thread at any time. If 2 threads access an object at the same time, there will be side effects — some of them are just weird, others will crash your app!

When would you use multithreading?

Multithreading is used when we can divide our job into several independent parts. For example, suppose you have to execute a complex database query for fetching data and if you can divide that query into sereval independent queries, then it will be better if you assign a thread to each query and run all in parallel.


1 Answers

I think you are pretty close to getting it to work.

There are only two hurdles:

  • thread::spawn will not allow sharing references
  • alive and loop for you to share in this design

The solution is two-fold:

  • split up things between the controller (Timer) and the worker (the closure)
  • share state between the two using Arc since references are forbidden

Here is a minimal example for you to toy with:

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

pub struct Timer {
    handle: Option<thread::JoinHandle<()>>,
    alive: sync::Arc<AtomicBool>,
}

impl Timer {
    pub fn new() -> Timer {
        Timer {
            handle: None,
            alive: sync::Arc::new(AtomicBool::new(false)),
        }
    }

    pub fn start<F>(&mut self, fun: F)
        where F: 'static + Send + FnMut() -> ()
    {
        self.alive.store(true, Ordering::SeqCst);

        let alive = self.alive.clone();

        self.handle = Some(thread::spawn(move || {
            let mut fun = fun;
            while alive.load(Ordering::SeqCst) {
                fun();
                thread::sleep(time::Duration::from_millis(10));
            }
        }));
    }

    pub fn stop(&mut self) {
        self.alive.store(false, Ordering::SeqCst);
        self.handle
            .take().expect("Called stop on non-running thread")
            .join().expect("Could not join spawned thread");
    }
}

fn main() {
    let mut timer = Timer::new();
    timer.start(|| println!("Hello, World!") );

    println!("Feeling sleepy...");
    thread::sleep(time::Duration::from_millis(100));

    println!("Time for dinner!");
    timer.stop();
}

I invite you to poke holes at it one at a time (ie, change one thing that is different from your example, check the error message, and try to understand how the difference solved it).

On the playground, it printed for me:

Feeling sleepy...
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Time for dinner!

Though I would not rely on (1) the number of times "Hello, World!" appears and (2) "Feeling sleepy..." appearing first.

And damned, is Atomic verbose... I kinda wish there was a get/set with SeqCst (the stronger ordering) available.

like image 102
Matthieu M. Avatar answered Oct 04 '22 09:10

Matthieu M.