网站建设和信息更新的通知,东莞做网页设计,广州网站优化效果,网页qq登陆保护功能怎么关闭Rc 和 Weak 源码详解
一个值需要被多个所有者拥有
rust中所有权机制在图这种数据结构中#xff0c;一个节点可能被多个其它节点所指向。那么如何表示图这种数据结构#xff1f;在多线程中#xff0c;多个线程可能会持有同一个数据#xff1f;如何解决这个问题。
Rc
rus…Rc 和 Weak 源码详解
一个值需要被多个所有者拥有
rust中所有权机制在图这种数据结构中一个节点可能被多个其它节点所指向。那么如何表示图这种数据结构在多线程中多个线程可能会持有同一个数据如何解决这个问题。
Rc
rust 通过使用引用计数智能指针 Rc 和 Arc 来解决上面的问题。当我们对一个被 Rc 所标识的数据进行 clone() 的时候并不会复制其内部数据只是增加引用计数而当一个 Rc 被 drop 的时候只会减少其引用计数直到引用计数为0此时才会真正清除对应的内存。
但是使用引用计数方案有一个问题那就是如何解决循环引用问题如果不了解引用计数方式管理内存的可以看这篇文章。rust 为了解决这个问题提供了弱引用(Weak)。它不拥有数据的所有权只产生弱引用计数。
我们来看一下 Rc 这个结构
#[cfg_attr(not(test), rustc_diagnostic_item Rc)]
#[stable(feature rust1, since 1.0.0)]
#[rustc_insignificant_dtor]
pub struct RcT: ?Sized {ptr: NonNullRcBoxT,phantom: PhantomDataRcBoxT,
}#[stable(feature rust1, since 1.0.0)]
implT: ?Sized !Send for RcT {}// Note that this negative impl isnt strictly necessary for correctness,
// as Rc transitively contains a Cell, which is itself !Sync.
// However, given how important Rcs !Sync-ness is,
// having an explicit negative impl is nice for documentation purposes
// and results in nicer error messages.
#[stable(feature rust1, since 1.0.0)]
implT: ?Sized !Sync for RcT {}首先Rc 是一个结构体可以看到它不满足 Send 和 Sync 这两个 trait这意味着 Rc 是不能跨线程的它只适用于单线程下的引用计数。这是 rust 专门为单线程场景设计的高性能引用计数器而多线程下需要 Arc (atomic reference counting)来实现多线程的引用计数。
另外一点就是 Rc 接受的泛型参数可以是大小未知unsized类型。Rc 结构体中有两个字段 ptr 和 phantom 。ptr 的类型是NonNullRcBoxT
pub struct NonNullT: ?Sized {pointer: *const T,
}也就是说 ptr 实际上是一个指向 RcBoxT 的非空指针。OK我们接着来看一下 RcBox 类型
struct RcBoxT: ?Sized {strong: Cellusize,weak: Cellusize,value: T,
}下面让我来详细解释这个结构体的各个字段 strong: Cellusize这个字段是一个 Cell 类型的包装用于存储强引用计数strong reference count。Cell 是 rust标准库提供的一种允许在不可变情况下修改其内部值的类型。强引用计数用于跟踪有多少个 Rc 实例仍然拥有对数据的引用。每当创建一个新的 Rc 引用时强引用计数会递增当 Rc 引用离开作用域或被丢弃时强引用计数递减。 weak: Cellusize这个字段是一个 Cell 类型的包装用于存储弱引用计数weak reference count。弱引用计数用于跟踪有多少个 Weak 引用Rc 的弱引用仍然存在但它不会阻止数据的销毁。与强引用不同当只有弱引用剩余时数据可以被销毁。每当创建一个新的 Weak 引用时弱引用计数会递增当Weak 引用离开作用域或被丢弃时弱引用计数递减。 value: T这是 Rc 包装的实际值的字段。Rc 用于共享这个值因此它包含在 RcBox 中。
既然强引用弱引用以及值都包含在 RcBox 中了那么 phantom: PhantomDataRcBoxT 的作用是什么
PhantomData 是一个泛型类型通常用于标记类型参数在运行时不实际占用内存。在这里它用于确保 RcBoxT 存在尽管它在运行时不占用内存。这是为了帮助Rust编译器进行正确的类型检查和生命周期分析。
pub struct PhantomDataT: ?Sized;正如我们所见PhantomData 是一个单元结构体它的大小是零字节不占用内存空间。
我们进一步来看一下 Rc 的构造方法看看它到底是如何做到让一个值可以有多个所有者按照之前的一个值只有一个所有者的模型当所有者生命周期结束的时候值就会被回收而 Rc 是在强引用计数到 0 的时候释放内存。
pub fn new(value: T) - RcT {// There is an implicit weak pointer owned by all the strong// pointers, which ensures that the weak destructor never frees// the allocation while the strong destructor is running, even// if the weak pointer is stored inside the strong one.unsafe {Self::from_inner(Box::leak(Box::new(RcBox { strong: Cell::new(1), weak: Cell::new(1), value })).into(),)}
}首先我们注意到 new 的实现代码是 unsafe 的这是因为 Box::leak 方法将 Box 中的数据泄漏leak出来而这个操作将绕过 Rust 的所有权和生命周期检查这样 RcBox 结构体数据将被泄漏到堆上使其在函数结束后继续存在而不是按正常方式被释放通过这种手段让 RcBox 拥有了足够长的生命周期以便在多个 Rc 实例之间正确地共享数据。
这段代码的注释中还告诉了我们所有强引用指针Rc 实例之间都存在一个隐式的弱引用指针。这个隐式的弱引用用于确保在强引用的析构函数运行期间弱引用不会释放数据即使在强引用指针中存储了一个弱引用。后面当我们介绍 Weak 析构函数的时候会看到它需要先读取 RcBox 中的数据。这样就防止弱引用析构执行的时候会访问到悬垂指针。
接着我们来看一下析构函数的代码。
fn drop(mut self) {unsafe {self.inner().dec_strong(); // 强引用计数减 1if self.inner().strong() 0 {// destroy the contained objectptr::drop_in_place(Self::get_mut_unchecked(self));// remove the implicit strong weak pointer now that weve// destroyed the contents.self.inner().dec_weak(); // 弱引用计数减 1if self.inner().weak() 0 {Global.deallocate(self.ptr.cast(), Layout::for_value(self.ptr.as_ref()));}}}
}如果强引用计数为零表示没有任何强引用指向数据了这意味着数据可以安全地被销毁。如果弱引用计数降至零表示没有任何弱引用指向数据将弱引用相关的资源清理掉。
既然 RcBox 中也存储了弱引用计数那么 Rc 肯定提供了从一个 Rc 获取到 弱引用的方法。实际上就是 downgrade 方法
pub fn downgrade(this: Self) - WeakT {this.inner().inc_weak();// Make sure we do not create a dangling Weakdebug_assert!(!is_dangling(this.ptr.as_ptr()));Weak { ptr: this.ptr }
}这个函数非常简单让弱引用计数加1然后保证不是悬垂指针之后用这个指针作为参数构造了一个 Weak 返回。这样就实现了从 Rc 中获取 Weak。
Weak
我们顺便来看一下弱引用Weak 用于创建弱引用通常与 Rc 智能指针一起使用。
pub struct WeakT: ?Sized {// This is a NonNull to allow optimizing the size of this type in enums,// but it is not necessarily a valid pointer.// Weak::new sets this to usize::MAX so that it doesn’t need// to allocate space on the heap. Thats not a value a real pointer// will ever have because RcBox has alignment at least 2.// This is only possible when T: Sized; unsized T never dangle.ptr: NonNullRcBoxT,
}Weak 也存储了一个指向 RcBox 的指针。看起来这是比 Rc 少了一个标记字段实际上它们的构造函数完全不同。
pub const fn new() - WeakT {Weak { ptr: unsafe { NonNull::new_unchecked(ptr::invalid_mut::RcBoxT(usize::MAX)) } }
}ptr::invalid_mut 函数来创建一个无效的指针其值被设置为 usize::MAX。这个无效指针用于表示一个 Weak 弱引用指针它不引用任何真实的数据但是用于表示一个空的 Weak 实例然后将其包装在 NonNull 中并返回作为 Weak 实例的一部分。这个无效的 Weak 实例通常用于初始化之后可以使用 upgrade 方法来尝试获取一个真实的强引用。
实际上在 Weak 结构体的注释中已经解释了 new 方法为什么会是这样。设置为 usize::MAX 的目的是为了避免在创建 Weak 时需要分配堆内存。由于 Weak 通常用于检查数据的存在性而不需要实际引用数据。
我们再来看一下析构函数
fn drop(mut self) {let inner if let Some(inner) self.inner() { inner } else { return };inner.dec_weak(); // 弱引用计数减1// the weak count starts at 1, and will only go to zero if all// the strong pointers have disappeared.if inner.weak() 0 {unsafe {Global.deallocate(self.ptr.cast(), Layout::for_value_raw(self.ptr.as_ptr()));}}
}let inner if let Some(inner) self.inner() { inner } else { return };这一行代码的目的是获取 Weak 引用内部的 RcBox 数据结构以便后续操作。self.inner() 方法用于获取内部数据如果存在则返回 Some(inner)否则返回 None。如果不存在内部数据说明这个 Weak 已经被销毁所以函数提前返回return。
如果弱引用计数降至零说明没有任何弱引用指向数据这意味着数据可以被释放。此时使用 Global.deallocate 来释放和 Weak 相关的内存。
前面说过可以通过 Rc 获取到一个弱引用那么同样当我们需要通过 Weak 来获取数据的时候就会产生一个 Rc。这个时候就需要使用 Weak 提供的 upgrade 方法。
pub fn upgrade(self) - OptionRcT {let inner self.inner()?;if inner.strong() 0 {None} else {unsafe {inner.inc_strong();Some(Rc::from_inner(self.ptr))}}
}首先尝试获取 RcBox 中的数据如果是 None则直接返回否则获取到 RcBox 中的数据进行强引用计数判断如果强引用计数为 0那么意味着数据被释放返回 None否则将强引用计数加 1然后返回一个 Rc 实例。
参考资料
Rust 官方文档: https://doc.rust-lang.org/std/rc/struct.Rc.html