我有一个不是Send的结构,因为它包含Rc。可以说Arc的开销太大,因此我想继续使用Rc。我仍然偶尔会在线程之间对该结构进行Send编码,但是仅当我可以验证Rc的编码为strong_count 1和weak_count 0时。

我想到的是(希望很安全)抽象:

mod my_struct {
    use std::rc::Rc;

    #[derive(Debug)]
    pub struct MyStruct {
        reference_counted: Rc<String>,
        // more fields...
    }

    impl MyStruct {
        pub fn new() -> Self {
            MyStruct {
                reference_counted: Rc::new("test".to_string())
            }
        }

        pub fn pack_for_sending(self) -> Result<Sendable, Self> {
            if Rc::strong_count(&self.reference_counted) == 1 &&
               Rc::weak_count(&self.reference_counted) == 0
            {
                Ok(Sendable(self))
            } else {
                Err(self)
            }
        }

        // There are more methods, some may clone the `Rc`!
    }

    /// `Send`able wrapper for `MyStruct` that does not allow you to access it,
    /// only unpack it.
    pub struct Sendable(MyStruct);

    // Safety: `MyStruct` is not `Send` because of `Rc`. `Sendable` can be
    //         only created when the `Rc` has strong count 1 and weak count 0.
    unsafe impl Send for Sendable {}

    impl Sendable {
        /// Retrieve the inner `MyStruct`, making it not-sendable again.
        pub fn unpack(self) -> MyStruct {
            self.0
        }
    }
}

use crate::my_struct::MyStruct;

fn main() {
    let handle = std::thread::spawn(|| {
        let my_struct = MyStruct::new();
        dbg!(&my_struct);

        // Do something with `my_struct`, but at the end the inner `Rc` should
        // not be shared with anybody.

        my_struct.pack_for_sending().expect("Some Rc was still shared!")
    });

    let my_struct = handle.join().unwrap().unpack();
    dbg!(&my_struct);
}

我在Rust playground上做了一个演示。

有用。我的问题是,它实际上安全吗?

我知道Rc仅由一个拥有者拥有,并且没有人可以更改它,因为其他线程无法访问它,因此我们将其包装到Sendable中,该Rc不允许访问所包含的值。

但是在某个疯狂的世界中,例如MyStruct可能在内部使用线程本地存储,因此这并不安全...因此,可以保证我可以做到这一点吗?

我知道我必须非常小心,不要介绍Send不是ojit_code的其他原因。

最佳答案

编号
为了能够跨线程发送Rc,需要验证多个点:

  • 不能有其他共享所有权的句柄(RcWeak)。
  • Rc的内容必须为Send
  • Rc的实现必须使用线程安全策略。

  • 让我们按顺序查看它们!
    保证没有别名
    尽管您自己的算法(自己检查计数)现在可以使用,但最好简单地询问Rc是否为别名。
    fn is_aliased<T>(t: &mut Rc<T>) -> bool { Rc::get_mut(t).is_some() }
    
    如果get_mut的实现以您未预见的方式更改,则将调整 Rc 的实现。
    可发送的内容
    尽管您目前对MyStruct的实现将String(即Send)放入Rc,但明天可能会更改为Rc<str>,然后所有赌注都关闭了。
    因此,可发送检查需要在Rc级别本身上实现,否则,您需要审核对Rc所持有内容的任何更改。
    fn sendable<T: Send>(mut t: Rc<T>) -> Result<Rc<T>, ...> {
        if !is_aliased(&mut t) {
            Ok(t)
        } else {
            ...
        }
    }
    
    线程安全的Rc内部函数
    而且...无法保证。
    由于Rc不是Send,因此可以通过多种方式优化其实现:
  • 可以使用线程本地竞技场分配整个内存。
  • 可以分别使用线程本地的竞技场分配计数器,以便无缝地与Box进行转换。
  • ...

  • AFAIK目前不是这种情况,但是API允许它,因此下一个版本肯定可以利用这一点。

    你该怎么办?
    您可以将pack_for_sending设为unsafe,并忠实地记录所有值得依赖的假设-我建议使用get_mut删除其中之一。然后,在每个新版本的Rust上,您都必须仔细检查每个假设,以确保您的用法仍然安全。
    或者,如果您不介意进行分配,则可以自己编写一个对Arc<T>的转换(请参阅Playground):
    fn into_arc(this:Rc)-> Result {
    Rc::try_unwrap(this).map(| t | Arc::new(t))
    }
    或者,您可以编写建议Rc <-> Arc转换的RFC!
    该API将是:
    fn Rc<T: Send>::into_arc(this: Self) -> Result<Arc<T>, Rc<T>>
    
    fn Arc<T>::into_rc(this: Self) -> Result<Rc<T>, Arc<T>>
    
    这可以在std内非常有效地进行,并可能对其他人有用。
    然后,您可以将MyStruct转换为MySendableStruct,只需移动字段并将Rc转换为Arc,然后发送到另一个线程,然后再转换回MyStruct
    而且您不需要任何unsafe ...

    关于rust - 如果strong_count为1而weak_count为0,使用包含 `Send`的 `Rc`结构是否安全?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58977260/

    10-14 09:54