如何在 Rust 中检查线程是否已经完成?

29
当我在Rust中创建一个线程时,会得到一个JoinHandle,这个对象很适合用来…等待(一个阻塞操作),但除此之外没什么其他用处。如何检查子线程是否已经退出(也就是说,JoinHandle.join()不会阻塞)?如果您知道如何终止一个子线程则可以获得额外的积分。
我想通过创建通道,向子进程发送一些内容,并捕获错误来实现此目的,但这似乎是不必要的复杂性和开销。
5个回答

20
自 Rust 1.7 起,标准库中没有 API 可以在不阻塞的情况下检查子线程是否已退出。
一种可移植的解决方法是使用 通道 向父进程发送消息,以表示子进程即将退出。 Receiver 具有非阻塞的 try_recv 方法。当 try_recv 收到消息时,您可以使用 JoinHandle 上的 join() 来检索线程的结果。
还有不稳定的平台特定扩展特性,可以让您获取原始线程句柄。然后,您需要编写平台特定代码来测试线程是否已退出。
如果您认为这个功能应该在Rust的标准库中,您可以提交RFC(一定要先阅读README!)。
如果您知道如何终止子线程,那就更好了。
在Rust中,线程是使用本地操作系统线程实现的。即使操作系统可能提供一种终止线程的方法,这样做是不明智的,因为线程分配的资源直到进程结束时才会被清理。

如果线程从未发送消息,但是 try_recv 返回 Disconnected,那我们可以得出结论该线程已终止吗? - iago-lito
2
不一定;一个线程可以早期放弃其发送者(例如使用 drop(sender);let _ = sender;)。 - Francis Gagné
非常正确。现在,如果这些是唯一的误报情况,那么我猜线程可以接收 _sender 作为参数并且从未使用它。这样,_sender 就会像生命线一样起作用...除非编译器决定立即释放它? - iago-lito
1
不,_sender 是一个有效的变量名(不像 _),所以析构函数将在该变量超出作用域时运行,除非你自己移出该变量。任何优化级别都无法改变这一点。 - Francis Gagné
1
太好了!所以我可以使用std而不会阻塞来检查子线程是否已退出。诀窍是let (rx, tx) = mpsc::channel();,将tx移动到子线程中,并定期检查rx.try_recv(),直到它产生Err(Disconnected)。在其侧面,线程需要接收_tx并承诺根本不使用它。我唯一能想到的缺点是,如果其他人编写了_tx,我无法强制执行子线程不会提前丢弃_tx - iago-lito

14

朋友们,这是可能的。使用Rust会在结束或崩溃时丢弃的引用计数器。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

更新: 我创建了thread-control 包来实现这个方法: https://github.com/DenisKolodin/thread-control


14

简短回答是目前还不可能。但这并不是真正需要解决的问题。

如果你知道如何终止一个子线程,就可以得到额外的加分。

千万不要

即使在支持终止线程的语言中 (比如Java),也建议不要这样做。

线程的执行通常会编码显示的交互点,并且经常有隐含的假设认为不会发生其他任何干扰。

最极端的例子当然是资源:天真的 "终止" 方法将停止执行线程;这意味着不释放任何资源。您可以考虑内存,这是您最不用担心的。相反,想象一下所有没有解锁并将在以后创建死锁的 Mutex...

另一种选择是向线程注入 panic,从而导致展开。但是,您不能 只是在任何时间点开始展开!程序必须定义 安全点,在这些点注入 panic 将被保证是安全的(在任何其他点注入它都意味着潜在地破坏共享对象);如何定义这样的安全点并在其中注入 panic 是本机语言中一个开放的研究问题,特别是那些在系统上执行 W^X(内存页面仅为可写或可执行,但绝不是两者兼备)。

总之,目前没有已知的安全方法(无论从内存还是从功能上)可以杀死一个线程。



3

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接