我有一个结构体不是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
。
2条答案
按热度按时间2izufjch1#
有多个点需要验证,以便能够跨线程发送
Rc
:1.不能有其他句柄(
Rc
或Weak
)共享所有权。Rc
的内容必须为Send
。Rc
的实现必须使用线程安全策略。让我们按顺序复习一下!
虽然您的算法--自己检查计数--目前可以正常工作,但最好只是询问
Rc
是否有别名。如果
Rc
的实现以您无法预见的方式发生变化,则get_mut
的实现将进行调整。虽然您的
MyStruct
实现目前将String
(即Send
)放入Rc
中,但明天它可能会变为Rc<str>
,然后所有的赌注都将落空。因此,可发送检查需要在
Rc
级别本身实现,否则您需要审计对Rc
所做的任何更改。Rc
内部构件**这一点......无法保证。
由于
Rc
不是Send
,因此可以通过多种方式优化其实现:Box
/从Box
转换。AFAIK,目前情况并非如此,但是API允许这样做,所以下一个版本肯定会利用这一点。
你该怎么办?
您可以将
pack_for_sending
设置为unsafe
,并忠实地记录所有依赖的假设--我建议使用get_mut
删除其中一个假设,然后,在Rust的每个新版本中,您必须仔细检查每个假设,以确保您的使用仍然安全。或者,如果您不介意进行分配,您可以自己编写一个到
Arc<T>
的转换(请参见Playground):或者,您可以编写一个RFC来建议
Rc <-> Arc
转换!API将为:
这可以在
std
中非常有效地实现,并且对其他人也有用。然后,将
MyStruct
转换为MySendableStruct
,只需移动字段并将Rc
转换为Arc
,发送到另一个线程,然后再转换回MyStruct
。你不需要任何
unsafe
...d4so4syb2#
Arc
和Rc
之间的唯一区别是Arc
使用原子计数器,计数器只在指针被克隆或删除时才被访问,因此在长生存期线程之间共享指针的应用程序中,两者之间的区别可以忽略不计。如果你从来没有克隆过
Rc
,那么在线程之间发送是安全的,但是,如果你能 * 保证 * 指针是唯一的,那么你就可以对一个原始值做出同样的保证,而根本不需要使用智能指针!这一切看起来相当脆弱,几乎没有什么好处;将来对代码的更改可能不符合您的假设,并且您将以未定义的行为结束。我建议您至少尝试使用
Arc
进行一些基准测试。只有在测量性能问题时才考虑这样的方法。您还可以考虑使用
archery
crate,它提供了一个引用计数指针,可以抽象原子性。