如何用Arc和Share创建循环引用? [英] How to create a cyclic reference with Arc and Weak?
本文介绍了如何用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]);
这不起作用,因为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屋!
查看全文