在一个采用"&mut self"(如drop)的方法中加入一个线程会导致"无法移出借来的内容"

use*_*932 5 multithreading rust

我想在new方法内部创建一个线程,并在结构被销毁后停止它:

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();
}
Run Code Online (Sandbox Code Playgroud)

这不编译,我不明白为什么:

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

我该如何解决这个错误?

今后,我将实施Drop的Foo,并调用stop()从drop().

Luk*_*odt 9

功能签名JoinHandle::join是:

fn join(self) -> Result<T>
Run Code Online (Sandbox Code Playgroud)

这意味着该方法self通过值(获取所有权/消费它)获取(接收者对象).但你只能向你借钱JoinHandle; 一个可变的,但仍然只是借入,而不是所有权.因此,您无法调用此方法,因为您无法将所有权从借用移到此join()方法中.

解决这个问题的一种简单方法是通过接受方法中self的值stop():

pub fn stop(self) {
    self.handle.join();
}
Run Code Online (Sandbox Code Playgroud)

但是你会发现实施时这是不可能的Drop,因为drop()有签名fn drop(&mut self)!坏消息!但是你可以使用一个小技巧,如下所述.请注意,加入主题drop()可能不是一个好主意!阅读Matthieu M.的答案,了解更多相关信息!

如果你仍然认为,无论出于何种原因,你真的想加入一个帖子drop(),你可以存储JoinHandle一个Option<T>来保存它是否已经加入.如果你有一个,Some(T)你可以T使用该方法从中获得(按值!)Option::take().然后你可以写:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)


Mat*_* M. 5

不要。


看起来似乎违反直觉,但是在析构函数中加入线程(或进程)通常不是一个好主意。

请注意,要求加入并不会导致线程自行停止;而是会导致线程自动停止。它只是在等待线程停止,而线程可能不会。发生这种情况的原因有多种:

  • 该句柄与其所控制的线程位于同一线程上,
  • 在当前线程应在其上发送信号的通道上停止的线程,
  • ...

是的,这是一个僵局。一个隐含的僵局。

一个特别令人讨厌的情况是,如果您当前的线程出现紧急情况(发生了无法预料的事情)。放卷开始...并阻塞!并且该线程本应清除的所有资源永远都处于困境。


更好的设计是创建一个显式join方法,该方法使用self(按值)消耗。Result如果连接导致错误,它还允许您返回。

并且为了让您的用户记住忘记加入,请panic!在Drop实现中明确加入。

那是:

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.");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:我知道破坏程序的恐慌是有争议的,但是,如果进程处于未知状态并希望继续前进,则中止进程更为安全。


如果您确实在我的警告下仍然想朝自己的脚开枪,那就加入吧drop。