【问题标题】:Passing two objects, where one holds a reference to another, into a thread将两个对象(其中一个对象持有对另一个对象的引用)传递给线程
【发布时间】:2015-06-14 02:01:44
【问题描述】:

我有两个对象,其中第二个对象需要第一个对象才能生存,因为它包含对第一个对象的引用。我需要将它们都移动到一个线程中,但编译器抱怨第一个活得不够长。代码如下:

use std::thread;

trait Facade: Sync {
    fn add(&self) -> u32;
}

struct RoutingNode<'a> {
    facade: &'a (Facade + 'a),
}

impl<'a> RoutingNode<'a> {
    fn new(facade: &'a Facade) -> RoutingNode<'a> {
        RoutingNode { facade: facade }
    }
}

fn main() {
    struct MyFacade;

    impl Facade for MyFacade {
        fn add(&self) -> u32 {
            999u32
        }
    }

    let facade = MyFacade;
    let routing = RoutingNode::new(&facade);

    let t = thread::spawn(move || {
        let f = facade;
        let r = routing;
    });

    t.join();
}

Playground

还有错误:

error: `facade` does not live long enough
  --> <anon>:27:37
   |
27 |     let routing = RoutingNode::new(&facade);
   |                                     ^^^^^^ does not live long enough
...
35 | }
   | - borrowed value only lives until here
   |
   = note: borrowed value must be valid for the static lifetime...

我相信我理解错误告诉我的内容:一旦facade 对象被移动到线程,引用将不再有效。但是我无法找到解决这个问题的有效解决方案,假设我想保持结构完好无损。

I asked this question on the Rust forums as well

【问题讨论】:

    标签: reference rust move-semantics lifetime lifetime-scoping


    【解决方案1】:

    The main problem is that once you have a reference to an item, you cannot move that item。让我们看一个简化的内存示例:

    let a = Struct1; // the memory for Struct1 is on the stack at 0x1000
    let b = &a;      // the value of b is 0x1000
    let c = a;       // This moves a to c, and it now sits on the stack at 0x2000
    

    哦不,如果我们尝试使用b 中的引用(仍然指向0x1000),那么我们将访问未定义的内存!这正是 Rust 帮助防止的一类错误 - Rust 万岁!

    如何修复它取决于您的实际情况。在您的示例中,我建议将facade 移动到线程中,然后在线程堆栈中的引用上创建RoutingNode

    let facade = MyFacade;
    
    let t = thread::spawn(move || {
        let f = facade;
        let r = RoutingNode::new(&f);
    });
    

    这是答案的一部分,人们通常会说“但演示代码不是我的真实代码所做的”,所以我期待额外的复杂性!

    不幸的是我不能使用这个解决方案,因为我需要在主线程中使用路由对象,然后再将其发送到另一个线程

    我在这里看到了一些选项。最直接的方法是让包装对象取得被包装对象的所有权,而不仅仅是引用:

    use std::thread;
    
    trait Facade: Sync {
        fn add(&self) -> u32;
    }
    
    struct RoutingNode<F> {
        facade: F,
    }
    
    impl<F> RoutingNode<F>
    where
        F: Facade,
    {
        fn new(facade: F) -> RoutingNode<F> {
            RoutingNode { facade }
        }
    }
    
    fn main() {
        struct MyFacade;
    
        impl Facade for MyFacade {
            fn add(&self) -> u32 {
                999u32
            }
        }
    
        let facade = MyFacade;
        let routing = RoutingNode::new(facade);
    
        let t = thread::spawn(move || {
            let r = routing;
        });
    
        t.join().expect("Unable to join");
    }
    

    Another option is to use scoped threads。这允许您拥有一个可以从闭包外部获得引用的线程,但必须在借用的变量超出范围之前加入。两个潜在的作用域线程提供者:

    使用横梁:

    extern crate crossbeam;
    
    let facade = MyFacade;
    let routing = RoutingNode::new(&facade);
    
    crossbeam::scope(|scope| {
        scope.spawn(|| {
            let r = routing;
        })
    });
    

    如果它对您的情况具有语义意义,我更喜欢第一个选项。我也喜欢第二种选择,因为线程的生命周期通常不需要是整个程序。

    【讨论】:

    • 是的,感谢您的编辑。确实不幸的是我不能使用这个解决方案,因为我需要在主线程中使用routing 对象,然后再将其发送到另一个线程。好吧,总有一些解决方法(比如在另一个线程中做需要做的事情,然后通过 mpsc::channel 发回结果),但我最感兴趣的是解决这个特殊问题。
    • 我认为可以解决这个问题的一种方法是,如果我将外观和节点放在一个盒子里,然后移动盒子。但我的 rust-fu 还不够好:)。我确实明白,我想如何实现这一点可能是不可能的,或者只是不是生锈的做事方式。
    • @PeterJankuliak 你根本不需要Box,但是在我下车后将一个移入另一个将是一个建议。 ^_^
    • 我最喜欢最后一个选项,因为它不会修改结构,非常感谢。
    • 您好,thread::scoped 不见了,今天的答案有些误导。我们是否留下了这里推荐的横梁和范围线程池stackoverflow.com/questions/32750829/…?谢谢
    猜你喜欢
    • 2015-09-15
    • 1970-01-01
    • 2012-09-15
    • 1970-01-01
    • 2019-05-02
    • 2021-12-07
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多