Quiero crear un hilo dentro del método new y detenerlo después de que se destruya la estructura:

use std::thread;

struct Foo {
    handle: thread::JoinHandle<()>,
}

impl Foo {
    pub fn new(name: &str) -> Foo {
        let name = name.to_string();
        Foo {
            handle: thread::spawn(move || {
                println!("hi {}", name);
            }),
        }
    }
    pub fn stop(&mut self) {
        self.handle.join();
    }
}

fn main() {
    let mut foo = Foo::new("test");
    foo.stop();
}

Esto no se compila, y no puedo entender por qué:

error[E0507]: cannot move out of borrowed content
  --> <anon>:15:9
   |
15 |         self.handle.join();
   |         ^^^^ cannot move out of borrowed content

Y en versiones más nuevas de Rust:

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

¿Cómo puedo corregir este error?

En el futuro, implementaré Drop para Foo, y llamaré a stop() desde drop().

5
user1244932 26 dic. 2016 a las 15:50

3 respuestas

La mejor respuesta

La firma de la función de JoinHandle::join es:

fn join(self) -> Result<T>

Esto significa que el método toma self (el objeto receptor) por valores (tomando la propiedad / consumiéndolo). Pero solo tiene un préstamo para su JoinHandle; uno mutable, pero que sigue siendo un préstamo, no la propiedad. Por lo tanto, no puede llamar a este método, porque no puede mover la propiedad de su préstamo a este método join().

Una manera fácil de arreglar eso es aceptar self por valor en el método stop() también:

pub fn stop(self) {
    self.handle.join();
}

¡Pero notará que esto no es posible al implementar Drop, porque drop() tiene la firma fn drop(&mut self)! ¡Gorrón! Pero hay un pequeño truco que puedes usar, que se describe a continuación. ¡Tenga en cuenta que unir hilos en drop() probablemente no sea una buena idea! ¡Lea la respuesta de Matthieu M. para obtener más información al respecto!

Si aún piensa, por cualquier razón, que realmente desea unirse a un hilo en drop(), puede almacenar el JoinHandle en un Option<T> para guardar si ya está unido o no. Si tiene un Some(T), puede obtener un T (¡por valor!) Utilizando el método Option::take(). Entonces puedes escribir:

fn drop(&mut self) {
    // `self.handle` has the type `Option<JoinHandle<()>>` here!
    if let Some(handle) = self.handle.take() {
        handle.join().expect("failed to join thread");
    }
}
9
Community 23 may. 2017 a las 11:53

No lo hagas .


Puede parecer contrario a la intuición, pero unir un hilo (o proceso) en un destructor es generalmente una mala idea .

Tenga en cuenta que solicitar unirse a no hace que el hilo se detenga solo; se trata de esperar a que el hilo se detenga, y el hilo puede que no. Hay varias razones por las que esto podría suceder:

  • el mango está en el mismo hilo que el que controla,
  • el hilo se detendrá esperando en un canal en el que el hilo actual debe enviar una señal,
  • ...

Sí, eso es un punto muerto. Un punto muerto implícito .

Una situación desagradable particular es si su hilo actual entra en pánico (ocurrió algo imprevisto). El desenrollado comienza ... ¡y se bloquea! Y todos los recursos que este hilo debía limpiar colgaban en el limbo, para siempre.


Un mejor diseño es crear un método explícito join, que consuma self (por valor). También le permite devolver un Result, en caso de que la unión provoque un error.

Y para que sus usuarios recuerden unirse explícitamente, panic! en la implementación Drop si se olvidaron.

Es decir:

impl Foo {
    fn join(mut self) -> std::thread::Result<()> {
        match self.handle.take() {
            Some(h) => h.join(),
            None => Ok(()),
        }
    }
}

impl Drop for Foo {
    fn drop(&mut self) {
        if self.handle.is_some() {
            panic!("You MUST call either join on `Foo` to clean it up.");
        }
    }
}

Nota: Soy consciente de que el pánico en los destructores es controvertido, sin embargo, es mucho más seguro abortar un proceso cuando está en un estado desconocido que continúa y espera lo mejor.


Si realmente , a pesar de mi advertencia, quiere dispararse en el pie, únase a drop.

5
dkim 10 nov. 2019 a las 14:55

El problema en la firma join:

fn join(self) -> Result<T>

Así que para arreglar su código, necesita algo como:

pub fn stop(self) {
    self.handle.join();
}
2
fghj 26 dic. 2016 a las 13:03