multithreading - 如何卡住 Rc 数据结构并跨线程发送它

标签 multithreading rust reference-counting

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

  1. 创建了一个大型数据结构,涉及大量临时对象并使用引用计数进行堆管理
  2. 数据结构是只读的,任何仍然存在的东西都被停放,结果结构被发送到另一个线程以供读取。

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

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

关于multithreading - 如何卡住 Rc 数据结构并跨线程发送它,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63433718/

相关文章:

java事件队列

c++ - 如何通过阻塞调用来控制线程的执行

rust - 如何从 Rust 中的特征获取函数指针?

rust - 借用对结构中属性的引用

当指针设置为 nil 时的 Objective-C 引用计数(无 ARC)

c++ - 重载运算符删除,否则如何杀死一只猫?

java - 在动态线程号中调用 ExecutorService.shutdown

rust - 如何声明一个采用三种不同泛型类型并返回两个较大数字的平方和的函数?

c++ - 如果对给定内存位置的写入发生在比页面粒度更细的位置,你能强制崩溃吗?

c# - 关于 SyncRoot 模式 : what is the correct way to use this pattern? 的一些说明