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.
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!
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.
I think you are pretty close to getting it to work.
There are only two hurdles:
thread::spawn
will not allow sharing referencesalive
and loop
for you to share in this designThe solution is two-fold:
Timer
) and the worker (the closure)Arc
since references are forbiddenHere 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With