首页 > 解决方案 > 无法从存储在向量中的 JoinHandles 加入线程 - Rust

问题描述

我正在编写一个程序,它从网站列表中抓取数据并将其存储到一个名为的结构Listing中,然后将其收集到一个名为Listings.

use std::{ thread,
           sync::{ Arc, Mutex }
         };

fn main() {
    // ... some declarations
    let sites_count = site_list.len(); // site_list is a vector containing the list of websites

    // The variable to be updated by the thread instances ( `Listing` is a struct holding the information ) 
    let listings: Arc<Mutex<Vec<Vec<types::Listing<String>>>>> = Arc::new(Mutex::new(Vec::new()));

    // A vector containing all the JoinHandles for the spawned threads
    let mut fetch_handle: Vec<thread::JoinHandle<()>> = Vec::new();

    // Spawn a thread for each concurrent website
    for i in 0..sites_count { 
        let slist = Arc::clone(&site_list);
        let listng = Arc::clone(&listings);
        fetch_handle.push(
            thread::spawn(move || {
                println!("⌛ Spawned Thread: {}",i);
                let site_profile = read_profile(&slist[i]);
                let results = function1(function(2)) // A long list of functions from a submodule that make the http request and parse the data into `Listing`
                listng.lock().unwrap().push(results);
            }));
    }
    
    for thread in fetch_handle.iter_mut() { 
        thread.join().unwrap();
    }

    // This is the one line version of the above for loop - yields the same error.
    // fetch_handle.iter().map(|thread| thread.join().unwrap()); 

    // The final println to just test feed the target struct `Listings` with the values
    println!("{}",types::Listings{ date_time: format!("{}", chrono::offset::Local::now()),
                                   category: category.to_string(),
                                   query: (&search_query).to_string(),
                                   listings: listings.lock().unwrap() // It prevents me from owning this variable
                                 }.to_json());
}

我偶然发现了错误

error[E0507]: cannot move out of `*thread` which is behind a mutable reference
   --> src/main.rs:112:9
    |
112 |         thread.join().unwrap();
    |         ^^^^^^ move occurs because `*thread` has type `JoinHandle<()>`, which does not implement the `Copy` trait

它阻止我在 thread.join() for 循环之后拥有该变量。

当我尝试分配检查输出类型时

let all_listings = listings.lock().unwrap()

all_listings报告一种 MutexGuard(在线程 for 循环中也是如此,但它允许我在其上调用向量方法)并且不允许我拥有数据。我更改了Listings结构中的数据类型以保存引用而不是拥有它。但似乎我对结构执行的操作.to_json()需要我拥有它的价值。Structlistings内部的类型声明是.ListingsVec<Vec<Listing<T>>

但是,当我将 移到块.join().unwrap()的末尾thread::spawn()或将其应用于 for 循环内的句柄(同时禁用 external .join())时,此代码可以正常工作。但这使得所有线程都在一条链中执行,这是不可取的,因为使用线程的主要目的是同时执行具有不同数据值的相同功能。

总的来说,我对 Rust 很陌生(我使用它已经 3 周了),这是我第一次实现多线程。在此之前,我只用 java 和 python 编写过单线程程序,所以如果可能的话,对菜鸟友好一点。然而,任何帮助表示赞赏:)。

标签: multithreadingrust

解决方案


我弄清楚需要发生什么。首先,对于这种事情,我同意into_iter做你想做的事,但 IMO 它掩盖了为什么原因是当你借用它时,它不拥有该值,这是结构join()上的方法所必需的JoinHandle<()>。你会注意到它的签名需要self而不是&mut self类似的东西。所以它需要那里的真实对象。

为此,您需要将对象从其Vec<thread::JoinHandle<()>>内部取出。如前所述,into_iter这样做是因为它“破坏”现有的Vec并接管它,因此它完全拥有内容,并且迭代返回要连接的“实际”对象而没有副本。但是您也可以一次拥有一个内容,remove如下所示:

while fetch_handle.len() > 0 {
    let cur_thread = fetch_handle.remove(0); // moves it into cur_thread
    cur_thread.join().unwrap();
}

这不是你for上面的循环。如果您想尝试,请链接操场中的完整示例。

我希望这更清楚地说明如何处理无法复制的东西,但方法需要完全拥有它们,以及将它们从集合中取出的问题。想象一下,如果您只需要结束其中一个线程,并且您知道要结束哪个线程,但又不想全部结束? Vec<_>::remove会工作,但into_iter不会。

感谢您提出一个让我思考的问题,并促使我自己去查找答案(并尝试)。我还在学习 Rust,所以这很有帮助。

编辑:

pop()使用and的另一种方法while let

while let Some(cur_thread) = fetch_handle.pop() {
    cur_thread.join().unwrap();
}

这从末端穿过它(pop将其从末端拉出,而不是从前面拉出),但也不会通过将矢量内容从前面拉出来重新分配或移动矢量内容。


推荐阅读