我有一个不是Send
的结构,因为它包含Rc
。可以说Arc
的开销太大,所以我想继续使用Rc
。我仍然偶尔希望在线程之间使用此结构Send
,但仅当我可以验证Rc
的强度为1且强度为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
中,该C0不允许访问包含价值。
但是在某个疯狂的世界中,Rc
例如可以在内部使用线程本地存储,因此这并不安全...因此,可以保证我可以做到这一点吗?
我知道我必须非常小心,不要引入某些其他原因使MyStruct
不是Send
。
Arc
和Rc
之间的唯一区别是Arc
使用原子计数器。仅在克隆或删除指针时才访问计数器,因此在仅在长寿命线程之间共享指针的应用程序中,两者之间的差异可以忽略不计。
如果您从未克隆过Rc
,则在线程之间发送是安全的。但是,如果可以guarantee指针是唯一的,那么就可以对原始值进行相同的保证,而无需使用任何智能指针!
这一切似乎都很脆弱,没有什么好处;将来对代码的更改可能不符合您的假设,并且您最终将获得未定义的行为。我建议您至少尝试使用Arc
进行一些基准测试。仅在评估性能问题时才考虑采用这种方法。
您还可以考虑使用archery
crate,它提供了一个引用计数的指针,该指针抽象了原子性。