【问题标题】:Sending a Boxed trait between threads在线程之间发送 Boxed 特征
【发布时间】:2021-04-02 00:15:29
【问题描述】:

我一直在玩 rust,我正在做一个非常愚蠢的程序,它结合了特征、结构、一些愚蠢的并发和泛型。在我在线程之间发送特征时遇到一些问题之前,一切对我来说都是可以理解的。

首先,我意识到我需要一个 Boxed Animals 向量来存储符合 Animal trait 的不同元素,好吧,我明白了,因为 trait 是一些其他特定结构的抽象,这些结构可以在“大小”等方面有所不同,因此我们必须将它们存储在堆中。 但是对我来说第一个奇怪的点是,因为我必须使用 Box 来存储特征,所以我还必须为 Boxed 特征实现我的特征(参见代码 cmets 上的 (*1))。

一旦我这样做了,程序对于编译器来说是正确的,但是我在运行时遇到了一些我不明白的问题。 我得到的错误是:

thread '<unknown>' has overflowed its stack
fatal runtime error: stack overflow
[1]    4175 abort (core dumped)  cargo run

代码是:

use std::thread;
use std::sync::mpsc::{Sender};
use std::sync::mpsc;
use std::time;

trait Animal {
    fn poop(&self) -> Poop;
}

#[derive(Debug)]
enum Weight {
    VeryLight,
    Light,
    Medium,
    Heavy,
    SuperHeavy,
}

#[derive(Debug)]
struct Poop {
    shape: String,
    weight: Weight,
}

struct Wombat;

impl Animal for Wombat {
    fn poop(&self) -> Poop {
        Poop {
            shape: "cubic".to_string(),
            weight: Weight::Light,
        }
    }
}

struct Cat;

impl Animal for Cat {
    fn poop(&self) -> Poop {
        Poop {
            shape: "cylindrical".to_string(),
            weight: Weight::VeryLight,
        }
    }
}

// (*1) This seemed weird for me and I'm not sure the 
// impl es correct
impl Animal for Box<dyn Animal + Send> {
    fn poop(&self) -> Poop {
        let t: &dyn Animal = self;
        // self.poop()
        t.poop()

    }
}

fn feed_animal<T> (a: T, tx: Sender<String>)
    where T: Animal + Send + 'static {
    
    thread::spawn(move || {
        thread::sleep(time::Duration::from_secs(2));
        tx.send(format!("{:?}", a.poop()))
    });
}

fn main() {
    let mut animals: Vec<Box<dyn Animal + Send>> = Vec::new();
    animals.push(Box::new(Wombat));
    animals.push(Box::new(Cat));
    
    let (tx, rx) = mpsc::channel();

    for a in animals {
        let txi = tx.clone();
        feed_animal(a, txi);
    }

    drop(tx);

    for r in rx {
        println!("The animal just pooped: {:?}", r);
    }
}

老实说,我对错误消息有点迷茫。通常当我在其他一些编程语言中看到这种错误是由于无限循环会溢出堆栈,但在这种情况下,我想我将 Boxed 特征“发送”到子线程的方式一定有一些错误这使得 rust 在运行时无法很好地处理子线程堆栈内存。我不确定。

任何正确方向的提示都将受到欢迎。 谢谢。

【问题讨论】:

    标签: rust


    【解决方案1】:

    我稍微更改了您的代码。您可以使用此playground link 进行检查。

    我认为你在这个函数签名中犯了一个错误,

    fn feed_animal<T> (a: T, tx: Sender<String>)
        where T: Animal + Send + 'static {
    }
    

    其中a 应该是Box&lt;T&gt;,因此您不需要为Box&lt;dyn Animal + Send&gt; 实现Animal

    编辑后的代码如下所示。

    use std::thread;
    use std::sync::mpsc::{Sender};
    use std::sync::mpsc;
    use std::time;
    
    trait Animal {
        fn poop(&self) -> Poop;
    }
    
    #[derive(Debug)]
    enum Weight {
        VeryLight,
        Light,
        Medium,
        Heavy,
        SuperHeavy,
    }
    
    #[derive(Debug)]
    struct Poop {
        shape: String,
        weight: Weight,
    }
    
    struct Wombat;
    
    impl Animal for Wombat {
        fn poop(&self) -> Poop {
            Poop {
                shape: "cubic".to_string(),
                weight: Weight::Light,
            }
        }
    }
    
    struct Cat;
    
    impl Animal for Cat {
        fn poop(&self) -> Poop {
            Poop {
                shape: "cylindrical".to_string(),
                weight: Weight::VeryLight,
            }
        }
    }
    
    fn feed_animal<T: ?Sized> (a: Box<T>, tx: Sender<String>)
        where T: Animal + Send + 'static {
        
        thread::spawn(move || {
            thread::sleep(time::Duration::from_secs(2));
            tx.send(format!("{:?}", a.poop()))
        });
    }
    
    fn main() {
        let mut animals: Vec<Box<dyn Animal + Send>> = Vec::new();
        animals.push(Box::new(Wombat{}));
        animals.push(Box::new(Cat{}));
        
        let (tx, rx) = mpsc::channel();
    
        for a in animals {
            let txi = tx.clone();
            feed_animal(a, txi);
        }
    
        for r in rx {
            println!("The animal just pooped: {:?}", r);
        }
    }
    

    【讨论】:

    • 好的,我明白你的意思了,没有考虑这个选项。但是,这是“惯用的”Rust 吗?两者中最好的选择是什么?因为,例如,我在定义“feed_animal”函数时的推理是......“我想接收一个符合 Animal 特征的类型 T 作为输入”;这是我对代码的规范。但是,推理会是......“好吧,我知道 Animal 是一个特征,因此我知道我必须接收一个 Box 作为输入,其中 T 符合 Animal 特征”。什么会更正确/惯用?
    • 您要做的是在新线程中喂动物并将它们的便便发送回主线程。然后,我认为将a 变成Box&lt;T&gt; 更有意义。您可以将 Box 视为堆分配的指针,它 1) 提供分配的所有权,2) 当 Box 超出范围时删除分配,以及 3) 确保固定大小。在 feed_animal() 中,当 Box 被移动到新线程时,T 的堆分配所有权也被移动到新线程。这是因为 Box 为 T 提供了所有权。您可以通过在 for 循环中调用 feed_animal 后使用 a 来验证这一点。
    【解决方案2】:

    让我们专注于这段代码:

    impl Animal for Box<dyn Animal + Send> {
        fn poop(&self) -> Poop {
            let t: &dyn Animal = self;
            // self.poop()
            t.poop()
    
        }
    }
    

    在这里,我们得到&amp;Box&lt;dyn Animal + Send&gt; 类型的self,将其转换为&amp;dyn Animal,并使其变为poop

    我相信您对间接级别感到困惑,尤其是额外的&amp; 的存在。通过将Box&lt;dyn Animal + Send&gt; 转换为dyn Animal,您将获得Box 的动态版本。稍后,您在该动态版本上调用 poop,这会使函数无意中递归。

    正确的代码是

    let t: &dyn Animal = &**self;
    

    顺便说一句,有一些工具可以在出现问题时获取堆栈跟踪。一种这样的解决方案,一种多用途的解决方案是运行valgrind target/debug/your-binary

    【讨论】:

    • 那么让我们看看我是否理解正确。 &** 引用: *) 获取 Box 的值(指向堆的指针) *) 获取指向堆的指针的值(指向 Animal) &) 获取对的引用存储在堆中的动物对吗?
    猜你喜欢
    • 2011-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-13
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多