问题描述
我的应用程序分两个阶段工作:
(为了更加具体,该应用程序是语言服务器,并且数据结构是处理的单个文件,这些文件是单线程处理的,但其结果必须跨线程传递。)
当前,我正在使用Arc<T>
来管理数据结构,但是由于阶段1既庞大又昂贵,并且是单线程的,所以我想将其切换为使用Rc<T>
。但是Rc
既不是Send
也不是Sync
,这是有充分的理由的,除非程序中基本上所有内容都使用线程安全的原语,否则我将无法发送数据结构或对其的引用。 / p>
我想说第二阶段开始后,我们不再需要引用计数了。线程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
。