【问题标题】:How to terminate or suspend a Rust thread from another thread?如何从另一个线程终止或挂起一个 Rust 线​​程?
【发布时间】:2014-11-29 18:00:30
【问题描述】:

编者注——这个例子是在 Rust 1.0 之前创建的,从那时起特定类型已经改变或被删除。一般问题和概念仍然有效。

我已经生成了一个线程,里面有一个无限循环和计时器。

thread::spawn(|| {
    let mut timer = Timer::new().unwrap();
    let periodic = timer.periodic(Duration::milliseconds(200));
    loop {
        periodic.recv();

        // Do my work here
    }
});

根据某些条件,经过一段时间后,我需要从程序的另一部分终止该线程。换句话说,我想退出无限循环。我怎样才能正确地做到这一点?另外,我怎么能暂停这个线程并在以后恢复它?

我尝试使用全局不安全标志来打破循环,但我认为这个解决方案看起来不太好。

【问题讨论】:

    标签: multithreading rust


    【解决方案1】:

    对于终止和暂停线程,您可以使用通道。

    外部终止

    在工作循环的每次迭代中,我们都会检查是否有人通过渠道通知了我们。如果是,或者如果通道的另一端超出范围,我们将中断循环。

    use std::io::{self, BufRead};
    use std::sync::mpsc::{self, TryRecvError};
    use std::thread;
    use std::time::Duration;
    
    fn main() {
        println!("Press enter to terminate the child thread");
        let (tx, rx) = mpsc::channel();
    
        thread::spawn(move || loop {
            println!("Working...");
            thread::sleep(Duration::from_millis(500));
            match rx.try_recv() {
                Ok(_) | Err(TryRecvError::Disconnected) => {
                    println!("Terminating.");
                    break;
                }
                Err(TryRecvError::Empty) => {}
            }
        });
    
        let mut line = String::new();
        let stdin = io::stdin();
        let _ = stdin.lock().read_line(&mut line);
    
        let _ = tx.send(());
    }
    

    暂停和恢复

    我们使用recv() 挂起线程,直到有东西到达通道。为了恢复线程,你需要通过通道发送一些东西;在这种情况下,单位值()。如果通道的发送端掉线,recv() 将返回Err(()) - 我们使用它来退出循环。

    use std::io::{self, BufRead};
    use std::sync::mpsc;
    use std::thread;
    use std::time::Duration;
    
    fn main() {
        println!("Press enter to wake up the child thread");
        let (tx, rx) = mpsc::channel();
        thread::spawn(move || loop {
            println!("Suspending...");
            match rx.recv() {
                Ok(_) => {
                    println!("Working...");
                    thread::sleep(Duration::from_millis(500));
                }
                Err(_) => {
                    println!("Terminating.");
                    break;
                }
            }
        });
    
        let mut line = String::new();
        let stdin = io::stdin();
        for _ in 0..4 {
            let _ = stdin.lock().read_line(&mut line);
            let _ = tx.send(());
        }
    }
    

    其他工具

    渠道是完成这些任务的最简单、最自然的 (IMO) 方式,但不是最有效的方式。您可以在 std::sync 模块中找到其他并发原语。它们属于比通道更低的级别,但在特定任务中可能更有效。

    【讨论】:

    • 当你的线程运行阻塞操作时,不知道它应该如何工作。
    • @hasufell 它不会,但是答案并没有说明。如果没有线程方面的一些合作,实际上不可能以安全且可移植的方式停止线程。渠道是此类合作的一种方式。
    【解决方案2】:

    理想的解决方案是Condvar。您可以在std::sync module 中使用wait_timeout,如pointed out by @Vladimir Matveev

    这是文档中的示例:

    use std::sync::{Arc, Mutex, Condvar};
    use std::thread;
    use std::time::Duration;
    
    let pair = Arc::new((Mutex::new(false), Condvar::new()));
    let pair2 = pair.clone();
    
    thread::spawn(move|| {
        let &(ref lock, ref cvar) = &*pair2;
        let mut started = lock.lock().unwrap();
        *started = true;
        // We notify the condvar that the value has changed.
        cvar.notify_one();
    });
    
    // wait for the thread to start up
    let &(ref lock, ref cvar) = &*pair;
    let mut started = lock.lock().unwrap();
    // as long as the value inside the `Mutex` is false, we wait
    loop {
        let result = cvar.wait_timeout(started, Duration::from_millis(10)).unwrap();
        // 10 milliseconds have passed, or maybe the value changed!
        started = result.0;
        if *started == true {
            // We received the notification and the value has been updated, we can leave.
            break
        }
    }
    

    【讨论】:

    • 只是为了与他人分享,@RajeevRanjan 提出的这个解决方案解决了我正在维护的基于 Rocket 的项目中的一个大问题。 Rocket 框架不包含关闭功能,因此我启动了一个新线程来处理服务器并使用Condvar 等待它。我尝试使用mpsc,但使用Condvar 更简单。代码可用here
    • @silvioprog 我没有提供这个建议;我只编辑了它。 RajeevRanjan 是解决方案的作者。
    【解决方案3】:

    我自己多次回到这个问题,这就是我认为解决 OP 的意图和其他人让线程自行停止的最佳实践的方法。基于公认的答案,Crossbeam 是对 mpsc 的一个很好的升级,允许克隆和移动消息端点。它还具有方便的勾选功能。这里真正的重点是它有非阻塞的 try_recv()。

    我不确定将消息检查器置于这样的操作循环的中间会有多大用处。我还没有发现 Actix(或以前的 Akka)可以真正停止线程,而无需 - 如上所述 - 让线程自己完成。所以这就是我现在正在使用的(在这里可以更正,仍在学习自己)。

    // Cargo.toml:
    // [dependencies]
    // crossbeam-channel = "0.4.4"
    
    use crossbeam_channel::{Sender, Receiver, unbounded, tick};
    use std::time::{Duration, Instant};
    
    fn main() {
        let (tx, rx):(Sender<String>, Receiver<String>) = unbounded();
        let rx2 = rx.clone();
    
        // crossbeam allows clone and move of receiver
        std::thread::spawn(move || {
            // OP:
            // let mut timer = Timer::new().unwrap();
            // let periodic = timer.periodic(Duration::milliseconds(200));
    
            let ticker: Receiver<Instant> = tick(std::time::Duration::from_millis(500));
    
            loop {
                // OP:
                // periodic.recv();
                crossbeam_channel::select! {
                    recv(ticker) -> _ => {
    
                        // OP: Do my work here
                        println!("Hello, work.");
    
                        // Comms Check: keep doing work?
                        // try_recv is non-blocking
                        // rx, the single consumer is clone-able in crossbeam
                        let try_result = rx2.try_recv();
                        match try_result {
                            Err(_e) => {},
                            Ok(msg) => {
                                match msg.as_str() {
                                    "END_THE_WORLD" => {
                                        println!("Ending the world.");
                                        break;
                                    },
                                    _ => {},
                                }
                            },
                            _ => {}
                        }
                    }
                }
            }
        });
    
        // let work continue for 10 seconds then tell that thread to end.
        std::thread::sleep(std::time::Duration::from_secs(10));
        println!("Goodbye, world.");
        tx.send("END_THE_WORLD".to_string());
    }
    

    使用字符串作为消息设备有点令人畏惧——对我来说。可以在枚举中执行其他挂起和重新启动操作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多