【问题标题】:How to loop over thread handles and join if finished, within another loop?如何在另一个循环中循环线程句柄并在完成后加入?
【发布时间】:2022-07-30 16:07:17
【问题描述】:

我有一个程序可以在循环中创建线程,并检查它们是否已完成,如果已完成则清理它们。请参阅下面的最小示例:

use std::thread;

fn main() {    

    let mut v = Vec::<std::thread::JoinHandle<()>>::new();
    for _ in 0..10 {
        let jh = thread::spawn(|| {
            thread::sleep(std::time::Duration::from_secs(1));
        });
        v.push(jh);
        for jh in v.iter_mut() {
            if jh.is_finished() {
                jh.join().unwrap();
            }
        } 
    }
}

这给出了错误:

error[E0507]: cannot move out of `*jh` which is behind a mutable reference
    --> src\main.rs:13:17
     |
13   |                 jh.join().unwrap();
     |                 ^^^------
     |                 |  |
     |                 |  `*jh` moved due to this method call
     |                 move occurs because `*jh` has type `JoinHandle<()>`, which does not implement the `Copy` trait
     |
note: this function takes ownership of the receiver `self`, which moves `*jh`
    --> D:\rust\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\std\src\thread\mod.rs:1461:17
     |
1461 |     pub fn join(self) -> Result<T> {

如何让借阅检查器允许这样做?

【问题讨论】:

    标签: rust


    【解决方案1】:

    JoinHandle::join 实际上使用 JoinHandle。 但是iter_mut() 只借用了向量的元素并保持向量的活动。因此,您的JoinHandles 只是被借用的,您不能在借用的对象上调用消费方法。

    您需要做的是在遍历向量时获取元素的所有权,以便它们可以被join() 使用。这是通过使用into_iter() 而不是iter_mut() 来实现的。

    第二个错误是您(可能不小心)在彼此内部编写了两个 for 循环,而它们应该是独立的循环。

    第三个问题稍微复杂一些。您无法检查线程是否已完成,然后按照您的方式加入。因此,我暂时删除了is_finished() 检查,并将再次讨论这个问题。

    这是你的固定代码:

    use std::thread;
    
    fn main() {
        let mut v = Vec::<std::thread::JoinHandle<()>>::new();
        for _ in 0..10 {
            let jh = thread::spawn(|| {
                thread::sleep(std::time::Duration::from_secs(1));
            });
            v.push(jh);
        }
    
        for jh in v.into_iter() {
            jh.join().unwrap();
        }
    }
    

    对完成的线程做出反应

    这个更难。如果您只想等到所有完成,那么上面的代码就是要走的路。

    但是,如果您必须立即对完成的线程做出反应,则基本上必须设置某种事件传播。您不想一遍又一遍地循环所有线程,直到它们全部完成,因为这称为 idle-waiting 并且会消耗大量计算能力。

    因此,如果您想实现这一目标,则必须解决两个问题:

    • join() 使用 JoinHandle(),这将留下不完整的 VecJoinHandles。这是不可能的,因此我们需要将JoinHandle 包装成一个实际上可以部分从向量中剥离出来的类型,例如Option
    • 我们需要一种方法向主线程发出一个新子线程已完成的信号,这样主线程就不必不断地迭代线程。

    总而言之,实现起来非常复杂和棘手。

    这是我的尝试:

    use std::{
        thread::{self, JoinHandle},
        time::Duration,
    };
    
    fn main() {
        let mut v: Vec<Option<JoinHandle<()>>> = Vec::new();
        let (send_finished_thread, receive_finished_thread) = std::sync::mpsc::channel();
    
        for i in 0..10 {
            let send_finished_thread = send_finished_thread.clone();
    
            let join_handle = thread::spawn(move || {
                println!("Thread {} started.", i);
    
                thread::sleep(Duration::from_millis(2000 - i as u64 * 100));
    
                println!("Thread {} finished.", i);
    
                // Signal that we are finished.
                // This will wake up the main thread.
                send_finished_thread.send(i).unwrap();
            });
            v.push(Some(join_handle));
        }
    
        loop {
            // Check if all threads are finished
            let num_left = v.iter().filter(|th| th.is_some()).count();
            if num_left == 0 {
                break;
            }
    
            // Wait until a thread is finished, then join it
            let i = receive_finished_thread.recv().unwrap();
            let join_handle = std::mem::take(&mut v[i]).unwrap();
            println!("Joining {} ...", i);
            join_handle.join().unwrap();
            println!("{} joined.", i);
        }
    
        println!("All joined.");
    }
    

    重要

    此代码只是一个演示。如果其中一个线程恐慌,它将死锁。但这表明这个问题是多么复杂。

    这可以通过使用防坠落来解决,但我认为这个答案已经够复杂了;)

    【讨论】:

    • 这真的很有趣,谢谢!我从这个答案中学到了很多东西。我将继续使用代码,看看我是否能想出一个优雅的解决方案。
    猜你喜欢
    • 2014-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-04
    • 1970-01-01
    • 1970-01-01
    • 2022-10-12
    相关资源
    最近更新 更多