I am quite new to concurrent programming, threads, shared memory, message passing and so on.
In my example certain events are not handled (messages are not printed) and I don’t understand why.
To motivate my code a little: The problem I want to solve (currently) is only a simple blinking LED connected to a Raspberry Pi. The main thread is a web server (axum) that handles messages and changes the led-state accordingly. I want to run the LED-control-code in another thread that listens to messages sent over a channel:
enum ThreadControl {
Quit,
LedMessage(LedState),
}
enum LedState {
Off,
On,
BlinkOnOff { period_ms: u16 },
// BreathInOutLinear { period_ms: u16 },
// BreathInOutLogarithmic { period_ms: u16 },
// FallingSawtoothLinear { period_ms: u16 },
// RisingSawtoothLinear { period_ms: u16 },
// AttackDecay { attack_ms: u16, decay_ms: u16 },
// ... more mindblowing blink-stuff that might
// become complicated and should run independently
}
Next I define a function that controls the LED in a loop and listens to ThreadControl
-messages sent over a message-passing-channel and spawn that function in a thread. While the led-control-thread is running, I start another thread that sends ThreadControl
-messages to change the LED’s behavior or request the function to stop the loop. (Maybe I don’t need another thread to send messages, I’m not sure).
use std::{
sync::mpsc::{channel, Receiver},
thread::{self, sleep, spawn},
time::{Duration, Instant},
};
fn run_led_thread(rx: Receiver<ThreadControl>) {
println!("hello from led control thread");
let mut pin_state = false; // later will be the actual GPIO pin
let mut led_state = LedState::Off;
let mut now = Instant::now();
let mut keep_running = true;
loop {
rx.iter().for_each(|msg| match msg {
ThreadControl::LedMessage(new_led_state) => {
println!("Handling led message");
led_state = new_led_state;
}
ThreadControl::Quit => {
println!("Quitting thread");
keep_running = false;
}
});
if keep_running {
match led_state {
LedState::Off => {
pin_state = false;
println!("off")
}
LedState::On => {
pin_state = true;
println!("on")
}
LedState::BlinkOnOff { period_ms: value } => {
if now.elapsed() > Duration::from_millis((value as u64) / 2) {
pin_state = !pin_state;
now = Instant::now();
match pin_state {
true => println!("blink: on"),
false => println!("blink: off"),
}
}
}
}
// avoid thread running at 100%
// sleep arbitrary duration
// maybe there's a better solution?
sleep(Duration::from_millis(5))
} else {
break;
}
}
}
When I run this example I don’t get the expected output. I get:
hello from led control thread
Handling led message
Handling led message
Handling led message
Quitting thread
What I would expect is
hello from led control thread
Handling led message
on
Handling led message
off
Handling led message
blink: on
blink: off
blink: on
... // and so on
Quitting thread
Somehow that second match
(which matches LedState
) is not working as no messages are printed.
What am I doing wrong?
Also:
Is this a clever solution in the first place, or is it rather stupid for this kind of problem. I noticed that receiver seems to have a kind of queue. In my case LED-example I don’t expect the queue to mostly be one item (or none at all).
In Rust there is also shared state with Arc<Mutex< ... >>
which I also considered. But somewhere I read “don’t communicate via shared memory”.