【问题标题】:How to check if a thread has finished in Rust?如何检查一个线程是否在 Rust 中完成?
【发布时间】:2016-06-23 08:14:57
【问题描述】:

当我在 Rust 中spawn 一个线程时,我得到一个JoinHandle,这对... 加入(阻塞操作)很有用,除此之外别无其他。如何检查子线程是否已从父线程退出(即JoinHandle.join() 不会阻塞)?如果您知道如何杀死子线程,则可以加分。

我想你可以通过创建一个通道、向孩子发送一些东西并捕获错误来做到这一点,但这似乎是不必要的复杂性和开销。

【问题讨论】:

    标签: multithreading rust


    【解决方案1】:

    从 Rust 1.7 开始,标准库中没有 API 来检查子线程是否已在没有阻塞的情况下退出。

    一种可移植的解决方法是使用channels 从孩子向父母发送一条消息,以表明孩子即将退出。 Receiver 有一个非阻塞的 try_recv 方法。当try_recv 确实收到消息时,您可以在JoinHandle 上使用join() 来检索线程的结果。

    还有一些不稳定的特定于平台的扩展特性可以让您获得原始线程句柄。然后,您必须编写特定于平台的代码来测试线程是否已退出。

    如果你认为这个功能应该在 Rust 的标准库中,你可以submit an RFC(一定要先阅读自述文件!)。

    如果您知道如何杀死子线程,则可以加分。

    Rust 中的线程是使用本机操作系统线程实现的。即使操作系统可能提供杀死线程的方法,这是个坏主意,因为线程分配的资源在进程之前不会被清理> 结束。

    【讨论】:

    • 如果线程从不发送消息,但try_recv产生Disconnected,我们是否可以断定线程已经终止了?
    • 不一定;线程可能会提前丢弃其发送者(例如,使用drop(sender);let _ = sender;)。
    • 非常正确。现在,如果这些是误报的唯一情况,那么我猜该线程可以接收_sender 作为参数并且从不使用它。这样,_sender 将充当生命线……除非编译器决定立即释放它?
    • 不,_sender 是一个有效的变量名(与 _ 不同),因此析构函数将在该变量超出范围时运行,而不是更早运行,除非您自己移出该变量。任何级别的优化都不会改变这一点。
    • 太棒了!所以我可以检查一个子线程是否已经退出而没有阻塞并且只有std。诀窍是let (rx, tx) = mpsc::channel();,将tx 端移动到子线程并定期检查rx.try_recv(),直到它产生Err(Disconnected)。另一方面,线程需要接收_tx 并保证根本不使用它。我能想到的唯一缺点是,如果我是由其他人编写的,我无法强制子线程不会提前删除_tx:\
    【解决方案2】:

    朋友们,这是可能的。使用 Rust 会在 endpanic 时丢弃的 refcounters。 100% 安全。示例:

    use std::time::Duration;
    use std::sync::Arc;
    use std::sync::atomic::{AtomicBool, Ordering};
    use std::thread;
    
    fn main() {
        // Play with this flag
        let fatal_flag = true;
        let do_stop = true;
    
        let working = Arc::new(AtomicBool::new(true));
        let control = Arc::downgrade(&working);
    
        thread::spawn(move || {
            while (*working).load(Ordering::Relaxed) {
                if fatal_flag {
                    panic!("Oh, my God!");
                } else {
                    thread::sleep(Duration::from_millis(20));
                    println!("I'm alive!");
                }
            }
        });
    
        thread::sleep(Duration::from_millis(50));
    
        // To stop thread
        if do_stop {
            match control.upgrade() {
                Some(working) => (*working).store(false, Ordering::Relaxed),
                None => println!("Sorry, but thread died already."),
            }
        }
    
        thread::sleep(Duration::from_millis(50));
    
        // To check it's alive / died
        match control.upgrade() {
            Some(_) => println!("Thread alive!"),
            None => println!("Thread ends!"),
        }
    }
    

    要点:https://gist.github.com/DenisKolodin/edea80f2f5becb86f718c330219178e2

    在操场上:https://play.rust-lang.org/?gist=9a0cf161ba0bbffe3824b9db4308e1fb&version=stable&backtrace=0

    UPD:我创建了实现这种方法的thread-control crate:https://github.com/DenisKolodin/thread-control

    【讨论】:

      【解决方案3】:

      目前还不能给出简短的答案。但这不是真正应该解决的问题。

      如果您知道如何杀死子线程,则可以加分。

      从不

      即使在支持杀死线程 (see Java here) 的语言中,也建议不要这样做。

      线程的执行通常使用显式交互点进行编码,并且通常隐式假设不会发生其他中断。

      最令人震惊的例子当然是资源:天真的“杀死”方法是停止执行线程;这意味着不释放任何资源。您可能会想到记忆,这是您最不担心的事情。想象一下,所有未解锁的Mutex 稍后会造成死锁......

      另一种选择是在线程中注入panic,这将导致展开。但是,您不能只是在任何时候开始放松!程序必须定义安全点,在这些点注入panic 可以保证是安全的(在任何其他点注入它都意味着可能破坏共享对象);如何定义这样的安全点并注入panic 在本机语言中存在一个开放的研究问题,尤其是那些在W^X 系统上执行的语言(其中内存页要么是可写的,要么是可执行的,但从不兼有)。

      总而言之,没有已知的方法可以安全地(无论是在内存方面还是在功能方面)终止线程。

      【讨论】:

        【解决方案4】:

        我认为 Arc 可以用来解决这个问题 如果线程退出,则引用计数器减一

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-02-15
          • 1970-01-01
          • 2021-07-13
          • 1970-01-01
          • 2010-12-09
          • 2017-05-20
          • 2017-12-10
          • 1970-01-01
          相关资源
          最近更新 更多