如果strong_count 为1 且weak_count 为0,包含`Rc` 的`Send` 结构是否安全?

mic*_*srb 7 unsafe rust

我有一个结构不是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);
}
Run Code Online (Sandbox Code Playgroud)

我在Rust playground做了一个演示。

有用。我的问题是,它真的安全吗?

我知道Rc它只由一个单一的onwer拥有,没有人可以在我的手中改变它,因为它不能被其他线程访问,我们将它包装成Sendable不允许访问包含的值。

但是在一些疯狂的世界中Rc,例如可以在内部使用线程本地存储,这将是不安全的......那么有没有保证我可以做到这一点?

我知道,我必须格外小心,不引入了一些额外的理由MyStruct来不Send

Mat*_* M. 7

不。

有多个点需要验证才能Rc跨线程发送:

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

让我们按顺序回顾它们!

保证没有混叠

虽然您的算法 - 自己检查计数 - 现在有效,但最好简单地询问Rc它是否有别名。

fn is_aliased<T>(t: &mut Rc<T>) -> bool { Rc::get_mut(t).is_some() }
Run Code Online (Sandbox Code Playgroud)

如果以您未预见的方式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 {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

线程安全的Rc内部结构

而且……不能保证。

由于Rc不是Send,它的实现可以通过多种方式进行优化:

  • 可以使用线程局部区域来分配整个内存。
  • 可以使用线程本地区域单独分配计数器,以便无缝地转换为/从Box.
  • ...

目前情况并非如此,AFAIK,但是 API 允许这样做,因此下一个版本肯定可以利用这一点。


你该怎么办?

您可以制定pack_for_sending unsafe并尽职尽责地记录所有依赖的假设——我建议使用get_mut删除其中之一。然后,在 Rust 的每个新版本中,您必须仔细检查每个假设以确保您的使用仍然安全。

或者,如果您不介意进行分配,您可以为Arc<T>自己编写一个转换(请参阅Playground):

fn into_arc(this: Rc) -> Result<Arc, Rc> { Rc::try_unwrap(this).map(|t| Arc::new(t)) }

或者,您可以编写一个 RFC 建议进行Rc <-> Arc转换!

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

这可以在内部非常有效地进行std,并且可以对其他人有用。

然后,您将从 转换MyStructMySendableStruct,只需移动字段并Rc随时转换Arc为 ,发送到另一个线程,然后转换回MyStruct.

你不需要任何unsafe......