我有一个不是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,// more fields... } impl MyStruct { pub fn new() -> Self { MyStruct { reference_counted: Rc::new("test".to_string()) } } pub fn pack_for_sending(self) -> Result { if Rc::strong_count( // 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!( // 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!(}
我在Rust playground上做了一个演示。
有效。我的问题是,它实际上安全吗?
我知道Rc仅由一个拥有者拥有,没有人可以更改它,因为其他线程无法访问它,因此我们将其包装到Sendable中不允许访问包含的值。
但是在某个疯狂的世界中,Rc可以例如在内部使用线程本地存储,因此这并不安全...所以可以保证我可以做到这一点吗?
我知道我必须非常小心,不要引入导致MyStruct不为Send的其他原因。
否。
需要验证多个点才能跨线程发送Rc:
让我们按顺序查看它们!
保证没有别名
虽然您的算法-自己检查计数-现在可以使用,但最好只询问Rc是否为别名。
fn is_aliased(t: &mut Rc) -> bool { Rc::get_mut(t).is_some() }
如果Rc的实现方式以您未预见的方式发生变化,则将调整get_mut的实现方式。
可发送的内容
虽然您目前对MyStruct的实施将String(即Send)放入Rc,但明天可能会更改为Rc,然后进行所有下注关闭。
因此,可发送检查需要在Rc级别上本身进行,否则,您需要审核对Rc所持有内容的任何更改。
fn sendable(mut t: Rc) -> Result 线程安全的Rc内部成员 那...不能保证。 由于Rc不是Send,因此可以通过多种方式优化其实现: AFAIK目前不是这种情况,但是API允许它,因此下一个版本肯定可以利用这一点。 你应该怎么做? 您可以制作pack_for_sending unsafe,并尽职记录所有可以依靠的假设-我建议使用get_mut删除其中之一。然后,在每个新版本的Rust上,您都必须仔细检查每个假设,以确保您的用法仍然安全。 或者... 您可以编写建议进行Rc Arc转换的RFC! API为: fn Rc::get_arc(this: Self) -> Option>fn Arc::get_rc(this: Self) -> Option 这可以在std内部非常有效地进行,并且对其他人也有用。 然后,您将要从MyStruct转换为MySendableStruct,只需移动字段并将Rc转换为Arc,然后发送到另一个线程,然后转换回到MyStruct。 您将不需要任何unsafe ... Arc和Rc之间的唯一区别是Arc使用原子计数器。仅在克隆或删除指针时才访问计数器,因此在仅在长寿命线程之间共享指针的应用程序中,两者之间的差异可以忽略不计。 如果您从未克隆Rc,则在线程之间发送是安全的。但是,如果可以保证指针是唯一的,那么就可以对原始值进行相同的保证,而无需完全使用智能指针! 这一切似乎都很脆弱,几乎没有收益。将来对代码的更改可能不符合您的假设,并且您最终将获得未定义的行为。我建议您至少尝试使用Arc做一些基准测试。仅在评估性能问题时才考虑采用这种方法。 您还可以考虑使用archery crate,它提供了一个引用计数的指针,该指针抽象了原子性。