我有一个不是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
,需要验证多个点:
Rc
或Weak
)。 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/