如何冻结Rc数据结构并跨线程发送它

问题描述

我的应用程序分两个阶段工作:

  1. 创建了一个大型数据结构,其中涉及大量临时对象,并使用引用计数进行堆管理
  2. 将数据结构设置为只读,将所有仍在运行的数据停放,并将结果结构发送到另一个线程进行读取。

(为了更加具体,该应用程序是语言服务器,并且数据结构是处理的单个文件,这些文件是单线程处理的,但其结果必须跨线程传递。)

当前,我正在使用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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...