我有一个结构不是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);
}
Run Code Online (Sandbox Code Playgroud)
我在Rust playground做了一个演示。
有用。我的问题是,它真的安全吗?
我知道Rc它只由一个单一的onwer拥有,没有人可以在我的手中改变它,因为它不能被其他线程访问,我们将它包装成Sendable不允许访问包含的值。
但是在一些疯狂的世界中Rc,例如可以在内部使用线程本地存储,这将是不安全的......那么有没有保证我可以做到这一点?
我知道,我必须格外小心,不引入了一些额外的理由MyStruct来不Send。
不。
有多个点需要验证才能Rc跨线程发送:
Rc或Weak)共享所有权。Rc必须是Send。Rc必须使用线程安全策略。让我们按顺序回顾它们!
保证没有混叠
虽然您的算法 - 自己检查计数 - 现在有效,但最好简单地询问Rc它是否有别名。
fn is_aliased<T>(t: &mut Rc<T>) -> bool { Rc::get_mut(t).is_some() }
Run Code Online (Sandbox Code Playgroud)
如果以您未预见的方式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 {
...
}
}
Run Code Online (Sandbox Code Playgroud)
线程安全的Rc内部结构
而且……不能保证。
由于Rc不是Send,它的实现可以通过多种方式进行优化:
Box.目前情况并非如此,AFAIK,但是 API 允许这样做,因此下一个版本肯定可以利用这一点。
你该怎么办?
您可以制定pack_for_sending unsafe并尽职尽责地记录所有依赖的假设——我建议使用get_mut删除其中之一。然后,在 Rust 的每个新版本中,您必须仔细检查每个假设以确保您的使用仍然安全。
或者,如果您不介意进行分配,您可以为Arc<T>自己编写一个转换(请参阅Playground):
fn into_arc(this: Rc) -> Result<Arc, Rc> { 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>>
Run Code Online (Sandbox Code Playgroud)
这可以在内部非常有效地进行std,并且可以对其他人有用。
然后,您将从 转换MyStruct为MySendableStruct,只需移动字段并Rc随时转换Arc为 ,发送到另一个线程,然后转换回MyStruct.
你不需要任何unsafe......
| 归档时间: |
|
| 查看次数: |
566 次 |
| 最近记录: |