10.1.3 克隆
2 分钟阅读
译文 · 基于 The Rustonomicon
克隆
原文链接: https://doc.rust-lang.org/nomicon/arc-mutex/arc-clone.html
基础代码有了,接下来要能 clone Arc。
基本上需要:
- 递增原子引用计数
- 用内部指针构造新的
Arc实例
首先访问 ArcInner:
let inner = unsafe { self.ptr.as_ref() };
更新原子引用计数:
let old_rc = inner.rc.fetch_add(1, Ordering::???);
这里该用什么 ordering?clone 时我们实际上没有需要原子同步的代码,因为 clone 期间不修改内部值。因此可用 Relaxed ordering,表示没有 happens-before 关系但仍是原子的。但在 Drop Arc 时,递减引用计数需要原子同步。详见 Arc 的 Drop 实现一节。关于原子关系与 Relaxed ordering,见原子操作一节。
因此代码变为:
let old_rc = inner.rc.fetch_add(1, Ordering::Relaxed);
需要再导入 Ordering:
| |
但当前实现有一个问题。若有人对一堆 Arc 调用 mem::forget 呢?迄今的代码(以及将要写的)假设引用计数准确反映内存中有多少个 Arc,但 mem::forget 会让这不再成立。因此从这个 Arc clone 出越来越多 Arc 却不 Drop、不递减引用计数时,可能溢出!这会导致释放后使用,极其糟糕!
为此需要检查引用计数不超过某个任意值(低于 usize::MAX,因为我们用 AtomicUsize 存引用计数),并做点什么。
标准库实现选择在任意线程上引用计数达到 isize::MAX(约为 usize::MAX 的一半)时直接 abort 程序(正常代码中极不可能,若发生则程序可能极度病态),假设不会有约 20 亿个线程(在部分 64 位机器上约为 9 quintillion)同时递增引用计数。我们采用同样策略。
实现很简单:
if old_rc >= isize::MAX as usize {
std::process::abort();
}
然后返回新的 Arc 实例:
Self {
ptr: self.ptr,
phantom: PhantomData
}
把这些包进 Clone 实现:
use std::sync::atomic::Ordering;
impl<T> Clone for Arc<T> {
fn clone(&self) -> Arc<T> {
let inner = unsafe { self.ptr.as_ref() };
// 这里用 relaxed ordering 没问题,因为我们不需要任何原子
// 同步——不修改也不访问内部数据。
let old_rc = inner.rc.fetch_add(1, Ordering::Relaxed);
if old_rc >= isize::MAX as usize {
std::process::abort();
}
Self {
ptr: self.ptr,
phantom: PhantomData,
}
}
}