如何冻结 Rc 数据结构并跨线程发送它
How to freeze an Rc data structure and send it across threads
我的应用程序分为两个阶段:
- 创建了一个大型数据结构,涉及大量临时对象并使用引用计数进行堆管理
- 数据结构是只读的,任何还活着的东西都被停放,结果结构被发送到另一个线程进行读取。
(为了更加具体,该应用程序是一个语言服务器,数据结构是处理单个文件的,这些文件是单线程处理的,但其结果必须跨线程传递。)
目前,我正在使用 Arc<T>
来管理数据结构,但由于阶段 1 庞大且昂贵且是单线程的,我想将其切换为使用 Rc<T>
。但是 Rc
既不是 Send
也不是 Sync
有充分的理由,我不能发送数据结构或对它的引用,除非基本上程序中的所有内容都使用线程安全原语。
我想推断在第 2 阶段开始后,我们不再需要引用计数;线程 1(所有者)不允许触及引用计数,线程 2(借用者)不允许克隆数据,只能查看它,因此它也不能触及引用计数。我知道 Rc
不会提供这组保证,因为您可以在给定共享引用的情况下克隆一个 Rc
。这个模式有安全的 API 吗?理想情况下,从第 1 阶段转到第 2 阶段时,我不必复制任何数据。
这是一个玩具实现,只是为了向其中添加一些代码。函数 phase1()
在返回类型为 T
的数据结构之前生成大量垃圾,然后在另一个线程的 phase2()
中以只读方式对其进行分析。如果您将此代码中的 Arc
更改为 Rc
,您将收到一个错误,因为它无法跨线程发送。
use std::sync::Arc;
use crossbeam::thread::scope; // uses the crossbeam crate for scoped threads
enum T { Nil, More(Arc<T>) }
fn phase1() -> T {
let mut r = T::Nil;
for i in 0..=5000 {
r = T::Nil;
for _ in 0..i { r = T::More(Arc::new(r)) }
}
r
}
fn phase2(mut t: &T) {
let mut n = 0;
while let T::More(a) = t {
n += 1;
t = a;
}
println!("length = {}", n); // should return length = 5000
}
fn main() {
let r = phase1();
scope(|s| { s.spawn(|_| phase2(&r)); }).unwrap();
}
我不太清楚你在大局中想要完成什么。我的要点是,您不想使用 Arc
只是为了在线程之间发送数据结构,这只会发生一次。可以通过将类型包装在您手动实现 Send
的另一种类型中来做到这一点。这真的非常不安全,因为编译器将无法防止竞争条件。
use std::rc::Rc;
use std::thread::spawn;
// Foo is not Send because it contains a Rc
struct Foo {
bar: Rc<bool>,
}
// Foowrapper is forced to be Send
struct FooWrapper {
foo: Foo,
}
unsafe impl Send for FooWrapper {}
fn main() {
println!("Hello, ");
// We can't send a Foo...
let foo = Foo {
bar: Rc::new(false),
};
// This blows everything up, and there is no
// protection by the compiler
// let secret_bar = Rc::clone(&foo.bar);
// ...but we can send a FooWrapper.
// I hereby promise that I *know* Foo is in a
// state which is safe to be sent! I really checked
// and no future updates in the code will harm me, ever!
let wrap = FooWrapper { foo };
spawn(move || {
// Unwrap the Foo.
let foo: Foo = wrap.foo;
println!("{:?}", foo.bar);
})
.join()
.unwrap();
}
在上面的例子中,我们发送的数据结构包含一个Rc
,而不是Send
。通过包裹在 FooWrapper
中,它变成 Send
。然而,我们 100% 确定 在 FooWrapper
被发送到另一个线程的那一刻,内部 Foo
是安全的发送。例如,如果主线程具有 bar
之一的克隆(例如 let secret_bar = Rc::clone(&foo.bar);
)并且在发送点之后持有它,则情况并非如此。这将允许两个线程放弃他们的 bar
版本不同步,破坏 Rc
.
我的应用程序分为两个阶段:
- 创建了一个大型数据结构,涉及大量临时对象并使用引用计数进行堆管理
- 数据结构是只读的,任何还活着的东西都被停放,结果结构被发送到另一个线程进行读取。
(为了更加具体,该应用程序是一个语言服务器,数据结构是处理单个文件的,这些文件是单线程处理的,但其结果必须跨线程传递。)
目前,我正在使用 Arc<T>
来管理数据结构,但由于阶段 1 庞大且昂贵且是单线程的,我想将其切换为使用 Rc<T>
。但是 Rc
既不是 Send
也不是 Sync
有充分的理由,我不能发送数据结构或对它的引用,除非基本上程序中的所有内容都使用线程安全原语。
我想推断在第 2 阶段开始后,我们不再需要引用计数;线程 1(所有者)不允许触及引用计数,线程 2(借用者)不允许克隆数据,只能查看它,因此它也不能触及引用计数。我知道 Rc
不会提供这组保证,因为您可以在给定共享引用的情况下克隆一个 Rc
。这个模式有安全的 API 吗?理想情况下,从第 1 阶段转到第 2 阶段时,我不必复制任何数据。
这是一个玩具实现,只是为了向其中添加一些代码。函数 phase1()
在返回类型为 T
的数据结构之前生成大量垃圾,然后在另一个线程的 phase2()
中以只读方式对其进行分析。如果您将此代码中的 Arc
更改为 Rc
,您将收到一个错误,因为它无法跨线程发送。
use std::sync::Arc;
use crossbeam::thread::scope; // uses the crossbeam crate for scoped threads
enum T { Nil, More(Arc<T>) }
fn phase1() -> T {
let mut r = T::Nil;
for i in 0..=5000 {
r = T::Nil;
for _ in 0..i { r = T::More(Arc::new(r)) }
}
r
}
fn phase2(mut t: &T) {
let mut n = 0;
while let T::More(a) = t {
n += 1;
t = a;
}
println!("length = {}", n); // should return length = 5000
}
fn main() {
let r = phase1();
scope(|s| { s.spawn(|_| phase2(&r)); }).unwrap();
}
我不太清楚你在大局中想要完成什么。我的要点是,您不想使用 Arc
只是为了在线程之间发送数据结构,这只会发生一次。可以通过将类型包装在您手动实现 Send
的另一种类型中来做到这一点。这真的非常不安全,因为编译器将无法防止竞争条件。
use std::rc::Rc;
use std::thread::spawn;
// Foo is not Send because it contains a Rc
struct Foo {
bar: Rc<bool>,
}
// Foowrapper is forced to be Send
struct FooWrapper {
foo: Foo,
}
unsafe impl Send for FooWrapper {}
fn main() {
println!("Hello, ");
// We can't send a Foo...
let foo = Foo {
bar: Rc::new(false),
};
// This blows everything up, and there is no
// protection by the compiler
// let secret_bar = Rc::clone(&foo.bar);
// ...but we can send a FooWrapper.
// I hereby promise that I *know* Foo is in a
// state which is safe to be sent! I really checked
// and no future updates in the code will harm me, ever!
let wrap = FooWrapper { foo };
spawn(move || {
// Unwrap the Foo.
let foo: Foo = wrap.foo;
println!("{:?}", foo.bar);
})
.join()
.unwrap();
}
在上面的例子中,我们发送的数据结构包含一个Rc
,而不是Send
。通过包裹在 FooWrapper
中,它变成 Send
。然而,我们 100% 确定 在 FooWrapper
被发送到另一个线程的那一刻,内部 Foo
是安全的发送。例如,如果主线程具有 bar
之一的克隆(例如 let secret_bar = Rc::clone(&foo.bar);
)并且在发送点之后持有它,则情况并非如此。这将允许两个线程放弃他们的 bar
版本不同步,破坏 Rc
.