如何用Arc和Share创建循环引用? [英] How to create a cyclic reference with Arc and Weak?

查看:0
本文介绍了如何用Arc和Share创建循环引用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个结构:

struct A { 
    map: HashMap<u32, Vec<B>>,
}

struct B {
    weak: Weak<A>
}

当构造A时,它将拥有几个B,每个都链接到刚刚构造的A,类似于:

let a = Arc::new(A { map: HashMap::new() });

let b1 = B { weak: Arc::downgrade(&a) };
let b3 = B { weak: Arc::downgrade(&a) };
let b2 = B { weak: Arc::downgrade(&a) };

a.map.insert(5, vec![b1, b2]);
a.map.insert(10, vec![b3]);

Playground

这不起作用,因为Arc不提供修改映射的方法。Arc::get_mut不起作用,因为Weak已构造为该值。

如何用B构造一个A?我试图避免在访问map时进行运行时检查,因为在构造之后,它将永远不会再被修改。我使用不安全代码或批准的夜间功能没有问题。

推荐答案

Arc::get_mut()如果您甚至已有Weak引用,则会失败,因此您需要考虑改用内部可变性。因为您使用的是Arc,所以我假设您是在多线程环境中,所以我将使用线程安全的RwLock

use std::sync::{Arc, Weak, RwLock};
use std::collections::HashMap;

struct A { 
    map: RwLock<HashMap<u32, Vec<B>>>,
}

struct B {
    weak: Weak<A>
}

现在您可以构造如下对象:

fn init_a(a: Arc<A>) -> Arc<A> {
    let b1 = B { weak: Arc::downgrade(&a) };
    let b2 = B { weak: Arc::downgrade(&a) };
    // extra block is required so that the Mutex's write lock is dropped 
    // before we return a
    {
        let mut map = a.map.write().unwrap();
        let vec = map.entry(0).or_insert(Vec::new());
        vec.push(b1);
        vec.push(b2);
    }
    a
}

fn main() {
    let mut a = Arc::new(A { map: RwLock::new(HashMap::new()) });
    a = init_a(a);
}

如果您真的想要消除Mutex的所有运行时开销,并且您不介意使用unsafe代码,您可以使用UnsafeCell。它没有开销,但它的接口需要一个unsafe块,并且它是代码中额外的一层展开。而且UnsafeCell不是Sync,所以您无法在线程之间共享它。

要解决这些问题,请确保在构建时只需要考虑UnsafeCell,就可以利用UnsafeCell的大小成本为零且不影响布局的优势。代替A,使用不同的构造类型,除了UnsafeCell之外,与A相同。然后,这些类型可以与mem::transmute互换使用。

use std::collections::HashMap;
use std::sync::{Arc, Weak};
use std::cell::UnsafeCell;
use std::mem;

struct A { 
    map: HashMap<u32, Vec<B>>,
}

struct B {
    weak: Weak<A>
}

impl A {
    fn new() -> Arc<A> {
        let a = A { map: HashMap:: new() };
        Self::init_a(Arc::new(a))
    }

    fn init_a(a: Arc<A>) -> Arc<A> {
        // Important: The layout is identical to A
        struct AConstruct {
            map: UnsafeCell<HashMap<u32, Vec<B>>>,
        }
        // Treat the object as if was an AConstruct instead
        let a: Arc<AConstruct> = unsafe { mem::transmute(a) };
        let map = unsafe { &mut *a.map.get() };
        // B's weak references are to Arc<A> not to Arc<AConstruct> 
        let weak_a: Weak<A> = unsafe { mem::transmute(Arc::downgrade(&a)) };

        // Actual initialization here
        let vec = map.entry(0).or_insert(Vec::new());
        let b1 = B { weak: weak_a.clone() };
        let b2 = B { weak: weak_a.clone() };
        vec.push(b1);
        vec.push(b2);

        // We're done. Pretend the UnsafeCells never existed
        unsafe { mem::transmute(a) }
    }
}

您也可以使用原始指针来做到这一点,但我觉得UnsafeCell更安全一些!当LLVM得到某些数据是不变的保证时,它会进行一些优化,而UnsafeCell会在它违反这些保证时发挥一些魔力来保护您。因此,我不能100%确定这样做的安全性:

fn init_a(a: Arc<A>) -> Arc<A> {
    // Raw IMMUTABLE pointer to the HashMap 
    let ptr = &a.map as *const HashMap<_, _>;
    // Unsafely coerce it to MUTABLE
    let map: &mut HashMap<_, _> = unsafe { mem::transmute(ptr) };
    let weak_a: Weak<A> = Arc::downgrade(&a);

    // Actual initialization here
    let vec = map.entry(0).or_insert(Vec::new());
    let b1 = B { weak: weak_a.clone() };
    let b2 = B { weak: weak_a.clone() };
    vec.push(b1);
    vec.push(b2);

    a
}

这篇关于如何用Arc和Share创建循环引用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆