10.1.4 丢弃

Arc 的 Drop

译文 · 基于 The Rustonomicon

丢弃

原文链接: https://doc.rust-lang.org/nomicon/arc-mutex/arc-drop.html

  现在需要递减引用计数,并在足够低时 drop 数据,否则数据会永远留在堆上。

  为此实现 Drop。

  基本上需要:

  1. 递减引用计数
  2. 若数据只剩一个引用,则:
  3. 用原子 fence 防止数据使用与删除的重排序
  4. 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,引用计数的递减与此 Acquire fence 同步。这意味着数据的使用发生在引用计数递减之前,引用计数递减发生在此 fence 之前,fence 发生在数据删除之前。

如 Boost 文档 所解释,

必须强制:在一个线程中通过现有引用对对象的任何可能访问,happens before 在另一线程中删除该对象。这通过 drop 引用后的「release」操作(通过该引用的任何访问显然必须在此之前发生)以及删除对象前的「acquire」操作来实现。

特别地,虽然 Arc 的内容通常不可变,但可能对 Mutex<T> 等做内部写入。由于 Mutex 在删除时不会被 acquire,我们不能依赖其同步逻辑让线程 A 的写入对线程 B 中运行的析构函数可见。

另请注意,这里的 Acquire fence 或许可换成 Acquire load,在高竞争情况下可能提升性能。见 2。

  做法如下:

1
2
3
# use std::sync::atomic::Ordering;
use std::sync::atomic;
atomic::fence(Ordering::Acquire);

  最后 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()); }
    }
}
最后修改 August 11, 2026: 更新 (70a5af133)