如果 strong_count 为 1 且 weak_count 为 0,则包含 `Rc` 的 `Send` 结构是否安全?
Is it safe to `Send` struct containing `Rc` if strong_count is 1 and weak_count is 0?
我有一个不是 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
。
Arc
和 Rc
之间的唯一区别是 Arc
使用原子计数器。只有在克隆或删除指针时才会访问计数器,因此在仅在长期线程之间共享指针的应用程序中,两者之间的差异可以忽略不计。
如果您从未克隆过 Rc
,在线程之间发送是安全的。但是,如果您可以 保证 指针是唯一的,那么您可以对原始值做出相同的保证,而根本不需要使用智能指针!
这一切似乎都很脆弱,没有什么好处;未来对代码的更改可能不符合您的假设,您最终会遇到未定义的行为。我建议您至少尝试使用 Arc
进行一些基准测试。只有在衡量性能问题时才考虑这样的方法。
您也可以考虑使用 archery
crate,它提供了一个对原子性进行抽象的引用计数指针。
没有
有多个点需要验证才能跨线程发送Rc
:
- 不能有其他句柄(
Rc
或 Weak
)共享所有权。
Rc
的内容必须是Send
。
Rc
的实现必须使用线程安全策略。
让我们按顺序回顾一下!
保证没有锯齿
虽然您的算法(您自己检查计数)目前有效,但最好简单地询问 Rc
它是否有别名。
fn is_aliased<T>(t: &mut Rc<T>) -> bool { Rc::get_mut(t).is_some() }
如果 Rc
的实施以您未曾预见的方式发生变化,get_mut
的实施将进行调整。
可发送内容
虽然您的 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
,其实现可以通过多种方式进行优化:
- 可以使用线程局部区域分配整个内存。
- 计数器可以使用线程局部区域单独分配,以便无缝转换 to/from
Box
.
- ...
据我所知,目前情况并非如此,但是 API 允许这样做,因此下一个版本肯定会利用这一点。
你该怎么办?
您可以 pack_for_sending
unsafe
,并尽职地记录所有依赖的假设——我建议使用 get_mut
删除其中之一。然后,在 Rust 的每个新版本上,您必须仔细检查每个假设以确保您的使用仍然安全。
或者,如果您不介意进行分配,您可以自己编写一个到 Arc<T>
的转换(参见 Playground):
fn into_arc(this: 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>>
这可以在 std
内部非常有效地完成,并且可以对其他人有用。
然后,您将从 MyStruct
转换为 MySendableStruct
,只需移动字段并将 Rc
转换为 Arc
,发送到另一个线程,然后转换回 MyStruct
.
而且您不需要任何 unsafe
...
我有一个不是 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
。
Arc
和 Rc
之间的唯一区别是 Arc
使用原子计数器。只有在克隆或删除指针时才会访问计数器,因此在仅在长期线程之间共享指针的应用程序中,两者之间的差异可以忽略不计。
如果您从未克隆过 Rc
,在线程之间发送是安全的。但是,如果您可以 保证 指针是唯一的,那么您可以对原始值做出相同的保证,而根本不需要使用智能指针!
这一切似乎都很脆弱,没有什么好处;未来对代码的更改可能不符合您的假设,您最终会遇到未定义的行为。我建议您至少尝试使用 Arc
进行一些基准测试。只有在衡量性能问题时才考虑这样的方法。
您也可以考虑使用 archery
crate,它提供了一个对原子性进行抽象的引用计数指针。
没有
有多个点需要验证才能跨线程发送Rc
:
- 不能有其他句柄(
Rc
或Weak
)共享所有权。 Rc
的内容必须是Send
。Rc
的实现必须使用线程安全策略。
让我们按顺序回顾一下!
保证没有锯齿
虽然您的算法(您自己检查计数)目前有效,但最好简单地询问 Rc
它是否有别名。
fn is_aliased<T>(t: &mut Rc<T>) -> bool { Rc::get_mut(t).is_some() }
如果 Rc
的实施以您未曾预见的方式发生变化,get_mut
的实施将进行调整。
可发送内容
虽然您的 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
,其实现可以通过多种方式进行优化:
- 可以使用线程局部区域分配整个内存。
- 计数器可以使用线程局部区域单独分配,以便无缝转换 to/from
Box
. - ...
据我所知,目前情况并非如此,但是 API 允许这样做,因此下一个版本肯定会利用这一点。
你该怎么办?
您可以 pack_for_sending
unsafe
,并尽职地记录所有依赖的假设——我建议使用 get_mut
删除其中之一。然后,在 Rust 的每个新版本上,您必须仔细检查每个假设以确保您的使用仍然安全。
或者,如果您不介意进行分配,您可以自己编写一个到 Arc<T>
的转换(参见 Playground):
fn into_arc(this: Rc) -> 结果
或者,您可以编写一个 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>>
这可以在 std
内部非常有效地完成,并且可以对其他人有用。
然后,您将从 MyStruct
转换为 MySendableStruct
,只需移动字段并将 Rc
转换为 Arc
,发送到另一个线程,然后转换回 MyStruct
.
而且您不需要任何 unsafe
...