10.1.4 丢弃
2 分钟阅读
译文 · 基于 The Rustonomicon
丢弃
原文链接: https://doc.rust-lang.org/nomicon/arc-mutex/arc-drop.html
现在需要递减引用计数,并在足够低时 drop 数据,否则数据会永远留在堆上。
为此实现 Drop。
基本上需要:
- 递减引用计数
- 若数据只剩一个引用,则:
- 用原子 fence 防止数据使用与删除的重排序
- drop 内部数据
首先访问 ArcInner:
let inner = unsafe { self.ptr.as_ref() };
递减引用计数。为简化代码,若 fetch_sub 的返回值(递减前的引用计数)不等于 1(表示我们不是最后一个引用),可直接返回。
if inner.rc.fetch_sub(1, Ordering::Release) != 1 {
return;
}
然后需要原子 fence,防止数据使用与删除的重排序。如标准库 Arc 实现所述:
这个 fence 用于防止数据使用与删除的重排序。因为它标记为
Release,引用计数的递减与此Acquirefence 同步。这意味着数据的使用发生在引用计数递减之前,引用计数递减发生在此 fence 之前,fence 发生在数据删除之前。如 Boost 文档 所解释,
必须强制:在一个线程中通过现有引用对对象的任何可能访问,happens before 在另一线程中删除该对象。这通过 drop 引用后的「release」操作(通过该引用的任何访问显然必须在此之前发生)以及删除对象前的「acquire」操作来实现。
特别地,虽然 Arc 的内容通常不可变,但可能对
Mutex<T>等做内部写入。由于 Mutex 在删除时不会被 acquire,我们不能依赖其同步逻辑让线程 A 的写入对线程 B 中运行的析构函数可见。另请注意,这里的 Acquire fence 或许可换成 Acquire load,在高竞争情况下可能提升性能。见 2。
做法如下:
| |
最后 drop 数据本身。用 Box::from_raw drop boxed 的 ArcInner<T> 及其数据。它接受 *mut T 而非 NonNull<T>,因此需用 NonNull::as_ptr 转换。
unsafe { Box::from_raw(self.ptr.as_ptr()); }
这是安全的,因为我们知道持有 ArcInner 的最后一个指针,且指针有效。
把这些包进 Drop 实现:
impl<T> Drop for Arc<T> {
fn drop(&mut self) {
let inner = unsafe { self.ptr.as_ref() };
if inner.rc.fetch_sub(1, Ordering::Release) != 1 {
return;
}
// 这个 fence 用于防止数据使用与删除的重排序。
atomic::fence(Ordering::Acquire);
// 这是安全的,因为我们知道持有 `ArcInner` 的最后一个指针,
// 且指针有效。
unsafe { Box::from_raw(self.ptr.as_ptr()); }
}
}