释放
现在我们需要一种方法来减少引用计数,并在引用计数足够低时释放数据,否则数据将永远驻留在堆上。
为此,我们可以实现 Drop
特征。
基本上,我们需要
- 减少引用计数
- 如果只剩下一个对数据的引用,则
- 对数据进行原子栅栏操作,以防止数据的使用和删除操作发生重排序
- 释放内部数据
首先,我们需要访问 ArcInner
let inner = unsafe { self.ptr.as_ref() };
现在,我们需要减少引用计数。为了简化代码,我们还可以在 fetch_sub
返回的值(递减前的引用计数)不等于 1
时返回(当我们不是对数据的最后一个引用时会发生这种情况)。
if inner.rc.fetch_sub(1, Ordering::Release) != 1 {
return;
}
然后,我们需要创建一个原子栅栏,以防止数据的使用和删除操作发生重排序。如标准库中 Arc
的实现所述
此栅栏是为了防止数据的使用和删除操作发生重排序。因为它被标记为
Release
,所以引用计数的减少与这个Acquire
栅栏同步。这意味着数据的使用发生在引用计数减少之前,而引用计数减少发生在这个栅栏之前,这个栅栏又发生在数据删除之前。如Boost 文档中所述,
重要的是要强制一个线程中对对象的任何可能访问(通过现有引用)在另一个线程中删除对象*之前*发生。这是通过在删除引用后进行“释放”操作(通过此引用对对象的任何访问显然必须在此之前发生)以及在删除对象之前进行“获取”操作来实现的。
特别是,虽然 Arc 的内容通常是不可变的,但可以对 Mutex 之类的对象进行内部写入
。由于 Mutex 在删除时不会被获取,因此我们不能依赖其同步逻辑来使线程 A 中的写入对线程 B 中运行的析构函数可见。 还要注意,这里的 Acquire 栅栏可能可以用 Acquire 加载操作代替,这可以在高度竞争的情况下提高性能。参见2。
为此,我们执行以下操作
#![allow(unused)] fn main() { use std::sync::atomic::Ordering; use std::sync::atomic; atomic::fence(Ordering::Acquire); }
最后,我们可以释放数据本身。我们使用 Box::from_raw
来释放装箱的 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;
}
// This fence is needed to prevent reordering of the use and deletion
// of the data.
atomic::fence(Ordering::Acquire);
// This is safe as we know we have the last pointer to the `ArcInner`
// and that its pointer is valid.
unsafe { Box::from_raw(self.ptr.as_ptr()); }
}
}