Home > Back-end >  Separate thread for loop in struct implementation
Separate thread for loop in struct implementation

Time:02-01

I'm working with a struct where I need to read the GPIO pin of a Raspberry Pi, and increment a 'register' within the struct every time the pin goes high. Concurrently with this, I would like to be able to sample the register every now and then to see what the current value is.

When implementing this, my thought was to spawn a thread that continuously loops checking if the pin has gone from Low to High, and increment the register from within the thread. Then, from the parent thread, I can read the value of the register and report it.

After doing some research, it seems that a scoped thread would not be the correct implementation of this, because the child thread would never hand over ownership of the register to the parent thread.

Rather, I believe I should use an Arc/Mutex combination guarding the register and only momentarily take control over the lock to increment the register. Is this the correct interpretation of multithreading in Rust?

Assuming the above is correct, I'm unsure of how to implement this in Rust.

struct GpioReader {
    register: Arc<Mutex<i64>>,
    input_pin: Arc<Mutex<InputPin>>,
}

impl GpioReader {
    pub fn new(input_pin: InputPin) -> Self {
        Self {
            register: Arc::New(Mutex::from(0)),
            input_pin: Arc::new(Mutex::from(input_pin))
        }
    }

    pub fn start(&self) {
        let pin = self.input_pin.lock().unwrap(); // ???
        let register = self.register.lock().unwrap(); // ???

        let handle = spawn(move || loop {
            match pin.read() { // ???
                High => register  = 1, // ???
                Low => (),
            }

            sleep(Duration::from_millis(SLEEP_TIME));
        });
        handle.join().expect("Failed to join thread.");
    }

    pub fn get_register(&self) -> i64 {
        let reg_val = self.register.lock().unwrap();

        return reg_val;
    }
}

Given the above, how do I declare the pin and register variables in such a way that I can read off the pin and increment the register within the loop? My best guess is I'll have to instantiate some kind of reference to these members of the struct outside of the loop, and then pass the reference into the loop at which point I can use the lock() method of the Arc.

Edit: Using RaspberryPi 3A running Raspbian. The InputPin in question is from the rppal crate.

CodePudding user response:

  • Mutex<i64> is an anti-pattern. Replace it with AtomicI64.
  • Arc is meant to be cloned with Arc::clone() to create new references to the same object.
  • Don't use shared ownership if not necessary. InputPin is only used from within the thread, so move it in instead.
  • I'm unsure why you do handle.join(). If you want it to continue in the background, don't wait for it with .join().
use std::{
    sync::{
        atomic::{AtomicI64, Ordering},
        Arc,
    },
    thread::{self, sleep},
    time::Duration,
};

use rppal::gpio::InputPin;

struct GpioReader {
    register: Arc<AtomicI64>,
    input_pin: Option<InputPin>,
}

const SLEEP_TIME: Duration = Duration::from_millis(1000);

impl GpioReader {
    pub fn new(input_pin: InputPin) -> Self {
        Self {
            register: Arc::new(AtomicI64::new(0)),
            input_pin: Some(input_pin),
        }
    }

    pub fn start(&mut self) {
        let register = Arc::clone(&self.register);
        let pin = self.input_pin.take().expect("Thread already running!");

        let handle = thread::spawn(move || loop {
            match pin.read() {
                High => {
                    register.fetch_add(1, Ordering::Relaxed);
                }
                Low => (),
            }

            sleep(SLEEP_TIME);
        });
    }

    pub fn get_register(&self) -> i64 {
        self.register.load(Ordering::Relaxed)
    }
}

If you want to stop the thread automatically when the GpioReader object is dropped, you can use Weak to signal it to the thread:

use std::{
    sync::{
        atomic::{AtomicI64, Ordering},
        Arc,
    },
    thread::{self, sleep},
    time::Duration,
};

use rppal::gpio::InputPin;

struct GpioReader {
    register: Arc<AtomicI64>,
    input_pin: Option<InputPin>,
}

const SLEEP_TIME: Duration = Duration::from_millis(1000);

impl GpioReader {
    pub fn new(input_pin: InputPin) -> Self {
        Self {
            register: Arc::new(AtomicI64::new(0)),
            input_pin: Some(input_pin),
        }
    }

    pub fn start(&mut self) {
        let register = Arc::downgrade(&self.register);
        let pin = self.input_pin.take().expect("Thread already running!");

        let handle = thread::spawn(move || loop {
            if let Some(register) = register.upgrade() {
                match pin.read() {
                    High => {
                        register.fetch_add(1, Ordering::Relaxed);
                    }
                    Low => (),
                }

                sleep(SLEEP_TIME);
            } else {
                // Original `register` got dropped, cancel the thread
                break;
            }
        });
    }

    pub fn get_register(&self) -> i64 {
        self.register.load(Ordering::Relaxed)
    }
}
  • Related