从另一个线程控制线程会导致 Rust 中出现意外行为

问题描述 投票:0回答:1

在我的示例中,某些事件未得到处理(消息未打印),我不明白为什么。 为了激励我的代码:我想要解决的问题(当前)只是一个连接到 Raspberry Pi 的简单闪烁 LED。主线程是一个 Web 服务器 (

axum
),它处理消息并相应地更改 LED 状态。我想在另一个线程中运行 LED 控制代码,该线程监听通过通道发送的消息:

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
}

接下来,我定义一个函数,该函数控制循环中的 LED 并侦听通过消息传递通道发送的

ThreadControl
消息,并在线程中生成该函数。当 led-control-thread 运行时,我启动另一个线程,发送
ThreadControl
消息来更改 LED 的行为或请求函数停止循环。 (也许我不需要另一个线程来发送消息,我不确定)。

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;
        }
    }
}

fn main() {
    let (tx, rx) = channel();

    // a thread that controls the LED and waits for new
    // instructions for LED behavior or Quit-Messate
    let handle_led_driver = spawn(|| run_led_thread(rx));

    // another thread that sends control messages.
    // (maybe this could be the main thread)
    let handle_control = spawn(move || {
        tx.send(ThreadControl::LedMessage(LedState::On)).unwrap();
        thread::sleep(Duration::from_millis(200));
        tx.send(ThreadControl::LedMessage(LedState::Off)).unwrap();
        thread::sleep(Duration::from_millis(200));
        tx.send(ThreadControl::LedMessage(LedState::BlinkOnOff {
            period_ms: 10,
        }))
        .unwrap();
        thread::sleep(Duration::from_millis(500));
        tx.send(ThreadControl::Quit).unwrap();
    });

    handle_led_driver.join().unwrap();
    handle_control.join().unwrap();
}

当我运行这个示例时,我没有得到预期的输出。我得到:

hello from led control thread
Handling led message
Handling led message
Handling led message
Quitting thread

想要期望的是

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

不知何故,第二个

match
(与
LedState
匹配)不起作用,因为没有打印任何消息。 我做错了什么?

还有: 这首先是一个聪明的解决方案,还是对于此类问题来说相当愚蠢。我注意到接收器似乎有一种队列。在我的 LED 示例中,我不希望队列主要是一项(或根本没有)。 在 Rust 中,我也考虑过与

Arc<Mutex< ... >>
共享状态。但我在某处读到“不要通过共享内存进行通信”。

multithreading rust concurrency message
1个回答
0
投票
    loop {
        rx.iter().for_each

正如 cafce25 评论的那样,这没有任何意义:通道的迭代器将阻塞并迭代,直到通道关闭,因此外循环是死代码。

为什么还要费心退出消息呢?当生产者终止并删除发送者时,将关闭通道,从而结束迭代。

最后睡眠似乎没什么用,它会做某事的唯一情况(在修复代码使其工作之后)是如果发送者每5毫秒生成一次以上消息,但如果发生这种情况你的接收者通道将被消息填满,因为消费者没有足够的吞吐量。如果您需要限制状态更改,我认为在去抖动窗口期间删除消息是个好主意。

或者保持睡眠,但然后使用集合点或容量非常小的通道,这样你就向生产者提供了背压,如果它只是用垃圾填满了程序的其余部分,那么它就没有理由全力运行。

© www.soinside.com 2019 - 2024. All rights reserved.