rust 如果strong_count为1且weak_count为0,则"发送“包含”Rc“的结构是否安全?

bcs8qyzn  于 2023-01-30  发布在  其他
关注(0)|答案(2)|浏览(102)

我有一个结构体不是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只由一个拥有者拥有,没有人可以在我的控制下改变它,因为它不能被其他线程访问,我们将它 Package 到Sendable中,这不允许访问包含的值。
但是在一些疯狂的世界里,Rc可能会在内部使用线程本地存储,这将是不安全的...所以我能保证我能做到这一点吗?
我知道我必须非常小心,不要引入一些额外的原因,使MyStruct不是Send

2izufjch

2izufjch1#

    • 没有**

有多个点需要验证,以便能够跨线程发送Rc
1.不能有其他句柄(RcWeak)共享所有权。

  1. Rc的内容必须为Send
  2. Rc的实现必须使用线程安全策略。
    让我们按顺序复习一下!
    • 保证没有别名**

虽然您的算法--自己检查计数--目前可以正常工作,但最好只是询问Rc是否有别名。

fn is_aliased<T>(t: &mut Rc<T>) -> bool { Rc::get_mut(t).is_some() }

如果Rc的实现以您无法预见的方式发生变化,则get_mut的实现将进行调整。

    • 可发送的内容**

虽然您的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/从Box转换。
  • ...

AFAIK,目前情况并非如此,但是API允许这样做,所以下一个版本肯定会利用这一点。
你该怎么办?
您可以将pack_for_sending设置为unsafe,并忠实地记录所有依赖的假设--我建议使用get_mut删除其中一个假设,然后,在Rust的每个新版本中,您必须仔细检查每个假设,以确保您的使用仍然安全。
或者,如果您不介意进行分配,您可以自己编写一个到Arc<T>的转换(请参见Playground):

fn into_arc<T>(this: Rc<T>) -> Result<Arc<T>, Rc<T>> {
    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>>

这可以在std中非常有效地实现,并且对其他人也有用。
然后,将MyStruct转换为MySendableStruct,只需移动字段并将Rc转换为Arc,发送到另一个线程,然后再转换回MyStruct
你不需要任何unsafe ...

d4so4syb

d4so4syb2#

ArcRc之间的唯一区别是Arc使用原子计数器,计数器只在指针被克隆或删除时才被访问,因此在长生存期线程之间共享指针的应用程序中,两者之间的区别可以忽略不计。
如果你从来没有克隆过Rc,那么在线程之间发送是安全的,但是,如果你能 * 保证 * 指针是唯一的,那么你就可以对一个原始值做出同样的保证,而根本不需要使用智能指针!
这一切看起来相当脆弱,几乎没有什么好处;将来对代码的更改可能不符合您的假设,并且您将以未定义的行为结束。我建议您至少尝试使用Arc进行一些基准测试。只有在测量性能问题时才考虑这样的方法。
您还可以考虑使用archery crate,它提供了一个引用计数指针,可以抽象原子性。

相关问题