夜雨聆风学习资料网

ARTICLE · 1074991

Rust `Arc` 源码解析:强引用、弱引用与原子计数

Rust `Arc` 源码解析:强引用、弱引用与原子计数

所属:带着问题读 Rust 源码

本文产物:读懂 Arc::clone、Arc::drop、Arc::downgrade 与 Weak::upgrade 的核心协议,并实现一个可运行的教学版 MiniArc<T>

Arc<T> 经常被概括成一句话:线程安全的引用计数智能指针。

这句话只解释了 Arc 为什么能被多个线程持有,没有解释 Weak<T> 存在时,数据和底层内存分别在什么时候释放。

假设 Arc 只有一个原子计数器,最后一个持有者把计数减到 0 后立即释放整块内存,Weak<T> 就没法继续工作。弱引用不拥有 T,但在 T 析构后,它仍要保存一个可以比较、可以尝试升级的指针。

标准库同时管理两个生命周期:

数据生命周期:由 strong 控制,strong == 0 时 Drop T
分配生命周期:由 weak 控制,weak == 0 时释放 ArcInner 内存

这里的 weak 也不等于用户创建的弱引用数量。只要还有强引用,所有强引用会共同持有一个隐式弱引用。最后一个 Arc 析构 T 时,这份计数保证底层分配仍然存在。

先看一轮完整的计数变化:

Arc::new(T)
strong = 1, weak = 1(隐式弱引用)

Arc::downgrade
strong = 1, weak = 2(隐式 1 + 显式 1)

最后一个 Arc 被 Drop
strong: 1 -> 0
Drop T
释放隐式弱引用,weak: 2 -> 1

最后一个 Weak 被 Drop
weak: 1 -> 0
释放 ArcInner 整块内存

后面的 clone、drop、downgrade 和 upgrade,都围绕这两个计数展开。


一、从四个问题开始读源码

本文围绕四个问题定位标准库里的关键实现:

  1. 1. Arc::clone 为什么只用 Relaxed?
  2. 2. 最后一个 Arc 为什么先析构 T,却不一定立即释放内存?
  3. 3. Weak::upgrade 怎样避免把已经死亡的对象从 0 恢复成 1?
  4. 4. 标准库为什么让 weak 从 1 开始,而不是从 0 开始?

本文对照的是当前工具链 Rust 1.95 的 alloc/src/sync.rs。具体辅助函数和注释会随版本演进,但核心状态机长期稳定。

把分配器、marker 等字段略去后,结构大致如下:

pub struct Arc<T: ?Sized> {
    ptr: NonNull<ArcInner<T>>,
    // allocator、marker 等字段略

}

pub
 struct Weak<T: ?Sized> {
    ptr: NonNull<ArcInner<T>>,
    // allocator 等字段略

}

struct
 ArcInner<T: ?Sized> {
    strong: AtomicUsize,
    weak: AtomicUsize,
    data: T,
}

Arc<T> 本身通常只是一个指向堆分配的指针。克隆 Arc 不会克隆 T,多个 Arc 指向同一个 ArcInner<T>。

栈                         堆

Arc A ───────┐
             ├────────> ArcInner {
Arc B ───────┘              strong: 2,
                            weak: 2,
Weak W ─────────────────>   data: T,
                         }

上图中的 weak: 2 由一个显式 Weak W 和所有强引用共同持有的一个隐式弱引用组成。

1.1 Arc<T> 能否跨线程,取决于 T

标准库只在 T: Send + Sync 时让 Arc<T> 实现 Send + Sync。

原因分别是:

  • • 多个线程能通过 Arc<T> 同时得到 &T,所以 T 必须是 Sync;
  • • 最后一个 Arc 可能在线程 B 析构 T,即使 T 在线程 A 创建,所以 T 必须是 Send。

因此 Arc<RefCell<T>> 不会因为外面包了一层 Arc 就变得可跨线程共享。需要共享可变状态时,通常使用 Arc<Mutex<T>>、Arc<RwLock<T>> 或内部原子类型。


二、为什么有强、弱两个计数

只使用一个强引用计数时,生命周期很简单:

strong > 0:T 和分配都存在
strong == 0:Drop T,并释放内存

加入 Weak<T> 后,strong == 0 只代表 T 已经死亡,不能代表指针所在的分配可以释放。

弱引用还需要安全执行:

let weak = Arc::downgrade(&value);
drop
(value);

assert!
(weak.upgrade().is_none());
drop
(weak);

调用 upgrade 时,weak 必须还能读取 strong。如果最后一个 Arc 已经把整块 ArcInner 释放,读取计数本身就是 use-after-free。

所以底层分配必须活到最后一个 Weak 被释放:

状态
T
 是否存在
ArcInner
 分配是否存在
Weak::upgrade
strong > 0
是
是
可能成功
strong == 0, weak > 0
否
是
必须返回 None
weak == 0
否
否
已经没有指针可调用

strong 负责 T,weak 负责 ArcInner 这块分配。

2.1 隐式弱引用解决了什么竞态

Arc::new 的核心初始化是:

ArcInner {
    strong: AtomicUsize::new(1),
    weak: AtomicUsize::new(1),
    data,
}

明明还没有调用 Arc::downgrade,为什么 weak 已经是 1?

初始的这一个 weak 代表“强引用集合共同持有的弱引用”。每个 Arc 不会各持有一份,因此克隆强引用只增加 strong,不会增加 weak。

它建立了下面这个不变量:

strong > 0  =>  weak > 0

于是最后一个强引用可以按固定顺序处理:

strong 1 -> 0
Drop T
释放强引用集合持有的隐式 weak
如果 weak 也变成 0,再释放 ArcInner

没有这层隐式所有权,最后一个强引用和最后一个显式弱引用可能分别认为对方会保持分配存活,释放协议会复杂得多。

2.2 用户看到的 weak_count 不包含隐式弱引用

概念上:

内部 weak = 显式 Weak 数量 + (strong > 0 ? 1 : 0)

公共 API 会扣掉这份内部计数:

let value = Arc::new(42);
assert_eq!
(Arc::weak_count(&value), 0);

let
 weak = Arc::downgrade(&value);
assert_eq!
(Arc::weak_count(&value), 1);

内部计数先后是 1 和 2,但公共 API 返回 0 和 1。

计数查询只适合诊断和测试。其他线程可以在查询返回后立即 clone、drop 或 upgrade,不能把一次计数快照当成并发授权。


三、Arc::clone:为什么 Relaxed 足够

核心路径可以缩成:

fn clone(&self) -> Arc<T> {
    let
 old = self.inner().strong.fetch_add(1, Ordering::Relaxed);
    check_overflow
(old);
    Arc { ptr: self.ptr }
}

Relaxed 只保证计数增量是原子的,不建立其他普通内存访问的发布与获取关系。

这里仍然足够,因为调用 clone 的前提是手里已经有一个有效的 Arc:

已有 Arc
  -> strong 至少为 1
  -> T 不会在 clone 过程中被析构
  -> 新 Arc 只是加入现有所有权集合

clone 不负责把 T 从一个线程发布到另一个线程。把原有 Arc 传给当前线程的过程,已经必须通过线程创建、channel、锁等方式满足跨线程同步要求。

这里有两套同步责任:

引用计数正确性:fetch_add 的原子性负责
T 的跨线程可见性:传递原 Arc 的同步机制负责

每次高频 clone 只需要原子地增加计数,不必额外支付 Acquire 或 SeqCst 的同步成本。

3.1 为什么要防计数溢出

如果强引用计数从 usize::MAX 回绕到 0,仍然存在大量 Arc 时,某个 drop 可能误判自己是最后一个引用,导致提前析构和释放。

安全抽象不能假设调用者永远不会大量 mem::forget。标准库设置了 isize::MAX 级别的软上限,并在极端情况下中止进程或拒绝继续增加。

计数一旦回绕,引用计数协议就会失效,并直接威胁内存安全。标准库必须在回绕之前终止增加。


四、Arc::drop:Release 递减,最后一个执行 Acquire

标准库的核心形状是:

fn drop(&mut self) {
    if
 self.inner().strong.fetch_sub(1, Ordering::Release) != 1 {
        return
;
    }

    atomic::fence(Ordering::Acquire);
    unsafe
 { self.drop_slow() };
}

为什么不是一次 fetch_sub(1, AcqRel)?

因为绝大多数 drop 并不是最后一个,它们只需要原子地交还所有权,不会读取析构所需的共享历史,也不会释放对象:

strong 8 -> 7:返回
strong 7 -> 6:返回
...
strong 2 -> 1:返回
strong 1 -> 0:只有这一条路径需要 Drop T

每个持有者使用 Release 递减,保证它在放弃引用前对对象的访问不会被重排到递减之后。最后一个持有者执行 Acquire fence,与之前的 Release 操作同步,然后才析构 T。

这尤其影响内部可变类型。Arc<T> 常见地保存 Mutex<T>、原子变量或其他内部可变对象。最后一个引用可能在线程 B 被释放,而此前对内部状态的访问发生在线程 A。

可以把同步链理解成:

线程 A 对对象的访问
    happens-before
线程 A strong.fetch_sub(Release)
    synchronizes-with
线程 B Acquire fence(线程 B 观察到自己是最后一个)
    happens-before
线程 B Drop T

只有最后一个 drop 支付 Acquire 成本,是引用计数实现中常见的优化模式。

4.1 判断条件为什么是返回值等于 1

fetch_sub 返回修改前的旧值。

返回 2:这次把 strong 从 2 减到 1,不是最后一个
返回 1:这次把 strong 从 1 减到 0,是最后一个

不能先 load,看到 1,再单独 fetch_sub。两个线程可能同时看到旧值并作出错误判断。原子读改写必须同时完成“递减”和“谁拿到最后一个”的选举。


五、最后一个 Arc 只析构 T,不一定释放分配

标准库的慢路径做了两件事:

unsafe fn drop_slow(&mut self) {
    let
 _implicit_weak = Weak { /* 指向同一分配 */ };
    ptr::drop_in_place(&mut inner.data);
}

局部 Weak 代表强引用集合共同持有的隐式弱引用。函数结束时,它的 Drop 会递减 weak。

这几步不能换序:

1. strong 已经变成 0,新的 upgrade 不能成功
2. Drop T
3. 释放隐式 weak
4. 如果没有显式 Weak,释放 ArcInner

如果还存在显式弱引用,第三步只会把 weak 从 2 减到 1,底层分配继续存在,但其中的 T 已经不能再访问。

5.1 局部 guard 怎样处理 T::drop panic

因为 T::drop 可能 panic。

如果实现写成:

drop_in_place(data);       // 这里可能 panic
release_implicit_weak
();   // 可能永远执行不到

那么析构 panic 会泄漏隐式弱引用,底层分配永远无法释放。

把隐式弱引用做成局部 guard 后,正常返回和 panic 展开都会执行 guard 的 Drop。这与错误路径清理未初始化数组、文件锁 guard、事务回滚 guard 是同一种 RAII 思路。

这里主要防止的是泄漏。若析构在另一个 panic 展开期间再次 panic,Rust 仍可能直接中止进程;Arc 无法替任意 Drop 实现消除双重 panic。


六、Weak::drop:最后一个弱引用才释放内存

弱引用释放的核心路径与强引用相似:

fn drop(&mut self) {
    if
 inner.weak.fetch_sub(1, Ordering::Release) != 1 {
        return
;
    }

    atomic::fence(Ordering::Acquire);
    deallocate
(inner);
}

区别是最后一步不再析构 T。T 已经在 strong 归零时析构,这里只释放 ArcInner 的内存和分配器相关状态。

Weak 延长的只有底层分配的生命周期:

Weak 不延长 T 的生命周期;
Weak 延长的是保存计数器与旧地址的分配生命周期。

弱引用可以用来打破拥有关系环。父节点用 Arc 拥有子节点,子节点用 Weak 指回父节点。反向边让分配暂时存活并可尝试升级,但不会阻止父对象在强引用归零时析构。


七、Weak::upgrade:只能从正数加一,绝不能复活 0

错误实现可能写成:

let old = inner.strong.fetch_add(1, Ordering::Relaxed);
if
 old == 0 {
    return
 None;
}

问题在于 fetch_add 已经把 0 改成了 1。此时 T 可能已经析构,计数却被重新变成正数,创建了一个指向死亡对象的伪 Arc。

标准库用 CAS 循环完成升级:

let mut current = inner.strong.load(Ordering::Relaxed);

loop
 {
    if
 current == 0 {
        return
 None;
    }

    match
 inner.strong.compare_exchange_weak(
        current,
        current + 1,
        Ordering::Acquire,
        Ordering::Relaxed,
    ) {
        Ok
(_) => return Some(Arc { ptr }),
        Err
(observed) => current = observed,
    }
}

CAS 把两个条件绑定成一个原子步骤:

只有当前 strong 仍等于这个非零值时,才把它加一。

如果最后一个强引用抢先把计数从 1 减到 0,CAS 会失败,下一轮观察到 0 并返回 None。一旦 strong 为 0,它就进入永久死亡状态,安全路径永远不会把它恢复成 1。

7.1 成功顺序为什么是 Acquire

当前标准库的 Weak::upgrade 成功路径使用 Acquire,一个重要原因是兼容 Arc::new_cyclic。

new_cyclic 的大致过程是:

先分配 ArcInner,strong = 0,data 未初始化
把一个暂时无法 upgrade 的 Weak 交给闭包
闭包构造 T,可把 Weak 存进 T
写入 data
用 Release 把 strong 从 0 发布为 1

之后 Weak::upgrade 的 Acquire CAS 与这次 Release 发布同步,保证成功升级的线程看到完整初始化的 T。

在 new_cyclic 的构造闭包内部调用 upgrade 必须得到 None,因为此时 strong 仍然是 0。闭包返回并完成数据发布后,升级才可能成功。

7.2 为什么 CAS 失败顺序可以是 Relaxed

失败时没有获得强引用,也不会读取 T。代码只需要拿到新的计数值继续重试或判断为 0,不依赖其他普通数据的可见性,因此失败顺序可以是 Relaxed。


八、downgrade 还要配合唯一性检查

在只实现核心协议的教学版中,downgrade 可以写成:

let old = inner.weak.fetch_add(1, Ordering::Relaxed);

因为调用者持有 &Arc<T>,强引用保证分配仍然存在。

当前标准库没有直接使用 fetch_add。它会对 weak 做 CAS,并处理 usize::MAX 哨兵。

原因在于 Arc::get_mut / Arc::is_unique 需要证明:

strong == 1
并且不存在任何显式 Weak

仅仅先读 weak == 1,再读 strong == 1 不够。另一个线程可能在两次读取之间执行 downgrade,创建一个能在未来 upgrade 的弱引用。

标准库会暂时把 weak 从 1 CAS 成 usize::MAX,相当于短暂锁住弱引用计数:

weak == 1
  -> CAS 为 usize::MAX,阻止并发 downgrade
  -> Acquire 读取 strong 是否等于 1
  -> Release 把 weak 恢复为 1

并发 downgrade 看到哨兵后自旋,等锁释放再增加弱引用。

源码里同时存在两层逻辑:

引用计数基本协议:downgrade 增加 weak
标准库完整实现:还要与唯一性检查、动态大小类型、分配器和原始指针 API 协作

这也是本文 Demo 不实现 get_mut 的原因。若只复制其中一半逻辑,很容易造出一个表面可用、但唯一性证明不成立的 API。


九、实现一个教学版 MiniArc<T>

配套 Demo 只覆盖定长 T、全局分配器和核心引用计数,不尝试替代标准库。

结构定义:

struct ArcInner<T> {
    strong: AtomicUsize,
    weak: AtomicUsize,
    data: ManuallyDrop<T>,
}

pub
 struct MiniArc<T> {
    ptr: NonNull<ArcInner<T>>,
    marker: PhantomData<ArcInner<T>>,
}

pub
 struct MiniWeak<T> {
    ptr: NonNull<ArcInner<T>>,
    marker: PhantomData<ArcInner<T>>,
}

这里使用 ManuallyDrop<T>,因为 T 和 ArcInner 的释放时机不同:

strong == 0:手动 Drop T
weak == 0:Box::from_raw 释放 ArcInner,但不能再次 Drop T

如果直接保存普通 T,最后重建 Box<ArcInner<T>> 时会再次自动析构字段,造成 double drop。

9.1 创建:两个计数都从 1 开始

pub fn new(data: T) -> Self {
    let
 inner = Box::new(ArcInner {
        strong: AtomicUsize::new(1),
        weak: AtomicUsize::new(1),
        data: ManuallyDrop::new(data),
    });

    Self
 {
        ptr: NonNull::new_unchecked(Box::into_raw(inner)),
        marker: PhantomData,
    }
}

Box::into_raw 转移分配所有权,之后不再由某个固定的 Box 变量负责清理,而是由强、弱引用计数共同决定何时重建 Box。

9.2 Clone:只增加 strong

impl<T> Clone for MiniArc<T> {
    fn
 clone(&self) -> Self {
        let
 old = self.inner().strong.fetch_add(1, Ordering::Relaxed);
        abort_on_overflow
(old);

        Self
 {
            ptr: self.ptr,
            marker: PhantomData,
        }
    }
}

克隆不会触碰 weak。十万个强引用仍然共同拥有同一个隐式弱引用。

9.3 最后一个强引用:guard 保证 panic 安全

impl<T> Drop for MiniArc<T> {
    fn
 drop(&mut self) {
        if
 self.inner().strong.fetch_sub(1, Ordering::Release) != 1 {
            return
;
        }

        fence
(Ordering::Acquire);
        let
 _implicit_weak = ImplicitWeakGuard { ptr: self.ptr };

        unsafe
 {
            ManuallyDrop::drop(&mut (*self.ptr.as_ptr()).data);
        }
    }
}

ImplicitWeakGuard 的 Drop 调用统一的 release_weak:

unsafe fn release_weak<T>(ptr: NonNull<ArcInner<T>>) {
    let
 inner = ptr.as_ref();
    if
 inner.weak.fetch_sub(1, Ordering::Release) != 1 {
        return
;
    }

    fence
(Ordering::Acquire);
    drop
(Box::from_raw(ptr.as_ptr()));
}

此时 ArcInner.data 是 ManuallyDrop<T>,重建并释放 Box 不会再次析构 T。

9.4 Upgrade:CAS 赢得一个新的强引用

pub fn upgrade(&self) -> Option<MiniArc<T>> {
    let
 inner = self.inner();
    let
 mut current = inner.strong.load(Ordering::Relaxed);

    loop
 {
        if
 current == 0 {
            return
 None;
        }

        match
 inner.strong.compare_exchange_weak(
            current,
            current + 1,
            Ordering::Acquire,
            Ordering::Relaxed,
        ) {
            Ok
(_) => return Some(MiniArc { ptr: self.ptr, marker: PhantomData }),
            Err
(observed) => current = observed,
        }
    }
}

只有 CAS 成功后,返回值才拥有一份强引用计数。CAS 失败不能提前构造 MiniArc,否则其 Drop 会递减一份从未获得的计数。

9.5 Deref 的安全依据

impl<T> Deref for MiniArc<T> {
    type
 Target = T;

    fn
 deref(&self) -> &T {
        unsafe
 {
            &*((&self.inner().data as *const ManuallyDrop<T>).cast::<T>())
        }
    }
}

这段 unsafe 依赖三个不变量:

MiniArc 的存在代表它拥有一个 strong
strong > 0 时 T 已初始化且尚未 Drop
共享引用只能得到 &T,不能无保护地写 T

MiniWeak 不能实现 Deref。它只保证分配存在,不保证 T 存在。必须先通过 CAS upgrade 获得强引用,再访问数据。


十、逐步走一遍引用计数

代码:

let a = MiniArc::new(String::from("config"));
let
 b = a.clone();
let
 w1 = MiniArc::downgrade(&a);
let
 w2 = w1.clone();

状态变化:

操作
strong
weak 内部值
显式 Weak
T
new
1
1
0
存活
a.clone()
2
1
0
存活
downgrade
2
2
1
存活
w1.clone()
2
3
2
存活
drop(a)
1
3
2
存活
drop(b)
0
2
2
被析构
drop(w1)
0
1
1
已死亡
drop(w2)
0
0
0
分配释放

如果在 drop(b) 之前调用 w1.upgrade(),CAS 会把 strong 从 1 加到 2,返回一个新 MiniArc。如果在 drop(b) 完成后调用,观察到 0,返回 None。

临界竞争只有两种合法胜者:

upgrade 先成功:strong 1 -> 2,drop 只能从 2 -> 1,T 继续存活
drop 先成功:strong 1 -> 0,upgrade 永久失败,T 被析构

不存在“drop 已经决定析构,但 upgrade 又把 0 变回 1”的第三种状态。


十一、Arc 管理共享所有权,写入需要额外同步

下面的类型仍然不能直接修改:

let value = Arc::new(Vec::<u8>::new());
// value.push(1); // 无法通过 Arc 获得 &mut Vec<u8>

Arc 负责多所有者共享和线程安全的生命周期管理,对共享写入不提供互斥。

常见组合各自表达不同语义:

类型
适合场景
Arc<T>
构造后只读的共享配置、索引、路由表
Arc<AtomicUsize>
一个可独立原子更新的数字
Arc<Mutex<T>>
需要维护复合不变量的共享可变状态
Arc<RwLock<T>>
读多写少,且确实能从并发读获益
ArcSwap<T>
高频读取、低频替换整份不可变快照

不要用 Arc<Mutex<PgPool>>、Arc<Mutex<Client>> 机械包裹本身已经支持并发克隆的句柄。先确认内部类型的 clone 是否本来就是共享连接池或共享驱动句柄。


十二、强引用环为什么会泄漏

引用计数只能释放计数能够归零的对象。

节点 A --Arc--> 节点 B
节点 B --Arc--> 节点 A

即使外部所有变量都被释放,A 和 B 仍各自拥有对方的一份强引用,strong 永远不为 0。Rust 保证这不会变成 use-after-free,但不会自动做追踪式垃圾回收。

树和图通常把一个方向改成 Weak:

父 --Arc--> 子
子 --Weak--> 父

是否使用 Weak,要先写清楚拥有关系:

哪个边负责让对象活着?用 Arc
哪个边只需要在对象还活着时访问?用 Weak

如果所有边都可以任意拥有,就要重新审视对象生命周期和显式关闭协议,或者改用 arena、图存储。只靠引用计数很难推断这类结构的回收时机。


十三、计数 API 不能替代同步

下面的写法有竞态:

if Arc::strong_count(&value) == 1 {
    // 错误地认为从这里开始独占

}

另一个线程可能在检查前后 clone、drop 或通过 Weak upgrade。计数是瞬时观测,不会锁住状态。

需要安全可变借用时使用:

Arc::get_mut(&mut value)

它会检查强引用,同时处理弱引用与并发 downgrade 的关系。需要写时复制时使用:

Arc::make_mut(&mut value)

不要根据 strong_count 手写一个不完整的 get_mut。

同理,Weak::strong_count() > 0 也不能保证下一句 upgrade() 成功。最后一个 Arc 可以在两句之间被释放。正确写法是直接匹配 upgrade 的返回值:

if let Some(value) = weak.upgrade() {
    use_value
(&value);
}

十四、原始指针 API 为什么危险

标准库提供 Arc::into_raw、Arc::from_raw、增加/减少强计数等底层 API,常用于 FFI 和侵入式数据结构。

使用这些 API 时,每一份逻辑所有权都必须与计数严格对应。

let arc = Arc::new(String::from("data"));
let
 ptr = Arc::into_raw(arc);

unsafe
 {
    let
 arc = Arc::from_raw(ptr);
    drop
(arc);
}

同一个计数不能执行两次 from_raw:

unsafe {
    let
 first = Arc::from_raw(ptr);
    let
 second = Arc::from_raw(ptr); // 错误:凭空制造第二份所有权
    drop
(first);
    drop
(second);
}

如果确实需要第二份强引用,必须先通过受支持的 API 增加强计数,并证明指针仍来自有效的 Arc 分配、类型与元数据匹配。

默认优先传递普通 Arc<T>。只有跨 FFI 或实现底层容器时才承担原始指针协议。


十五、运行 Demo

源码结构:

mini-arc-demo
├── Cargo.toml
├── README.md
└── src
    ├── lib.rs
    └── main.rs

运行:

cd workspaces/mini-arc-demo
cargo run
cargo test

默认输出:

new: strong=1, weak=0
clone + downgrade: strong=2, weak=1
after one Arc drops: shared configuration
upgrade: strong=2, value=shared configuration
after all Arc values drop: upgrade=false

单元测试覆盖:

多个强引用共享同一个 T,且 T 只析构一次
Weak 不延长 T 的生命周期
upgrade 成功后确实拥有一份新 strong
多线程 clone、访问与 drop
T::drop panic 时仍释放隐式弱引用

Demo 没有实现:

动态大小类型 T: ?Sized
自定义 Allocator
Arc::new_cyclic
Arc::get_mut / make_mut 的弱计数锁
into_raw / from_raw
标准库全部溢出、静态空切片和平台工具适配

Demo 主动缩小了范围,只保留两阶段生命周期和原子计数协议,方便逐条核对实现。


十六、源码阅读检查清单

[ ] 是否区分了 T 的生命周期与 ArcInner 分配的生命周期
[ ] 是否知道 weak 内部计数包含一个隐式弱引用
[ ] Arc::clone 是否只增加 strong,而不增加 weak
[ ] 引用计数是否有溢出防护,绝不能允许回绕
[ ] 非最后一个 Arc::drop 是否只做 Release 递减
[ ] 最后一个 Arc 是否在 Drop T 前执行 Acquire 同步
[ ] T 析构后是否释放隐式 weak
[ ] T::drop panic 时是否仍能执行隐式 weak 清理
[ ] 最后一个 Weak 是否只释放分配,不再次 Drop T
[ ] Weak::upgrade 是否使用 CAS,绝不把 strong 从 0 改回 1
[ ] upgrade 成功后才构造新的 Arc 所有权
[ ] 是否理解 get_mut 还要与 downgrade 和显式 Weak 协作
[ ] 是否没有把 strong_count / weak_count 当成并发锁
[ ] 是否明确 Arc 只解决共享所有权,不自动提供共享可变性
[ ] 对象图中的拥有边与观察边是否分别使用 Arc 和 Weak
[ ] 原始指针转换是否做到一次所有权对应一次计数

十七、把计数协议串起来

读 Arc 源码时,可以盯住四个计数变化:

clone:strong 加一,已有 Arc 保证 T 还活着
drop:strong 从 1 变成 0,析构 T,再交还隐式 weak
upgrade:只允许把非零 strong 加一,0 不能恢复成 1
weak drop:weak 从 1 变成 0,释放 ArcInner 分配

Release 用来交还引用,最后一个持有者再用 Acquire 接住此前线程的访问。Weak 只能保证计数器和底层地址还在;要访问 T,必须让 upgrade 先拿到一份新的强引用。

参考

  • • std::sync::Arc
  • • std::sync::Weak
  • • Rust 标准库 alloc/src/sync.rs
  • • Rustonomicon: Atomics
  • • std::sync::atomic::Ordering

相关学习资料