10.1.2 基础代码
Arc 的基础实现
2 分钟阅读
译文 · 基于 The Rustonomicon
基础代码
原文链接: https://doc.rust-lang.org/nomicon/arc-mutex/arc-base.html
布局已定,来写 Arc 的基础代码。
构造 Arc
首先要有构造 Arc<T> 的方式。
这很简单:把 ArcInner<T> box 起来,得到指向它的 NonNull<T> 指针。
impl<T> Arc<T> {
pub fn new(data: T) -> Arc<T> {
// 引用计数从 1 开始,因为第一个引用就是当前指针。
let boxed = Box::new(ArcInner {
rc: AtomicUsize::new(1),
data,
});
Arc {
// 这里调用 `.unwrap()` 没问题,因为 `Box::into_raw`
// 返回的指针保证非 null。
ptr: NonNull::new(Box::into_raw(boxed)).unwrap(),
phantom: PhantomData,
}
}
}
Send 与 Sync
我们在做并发原语,需要能跨线程发送。因此可实现 Send 与 Sync 标记 trait。更多信息见 Send 与 Sync 一节。
这样是合理的,因为:
- 仅当且仅当它是引用该数据的唯一
Arc时(只在Drop中发生),才能拿到Arc内部值的可变引用 - 我们对共享可变引用计数使用原子操作
unsafe impl<T: Sync + Send> Send for Arc<T> {}
unsafe impl<T: Sync + Send> Sync for Arc<T> {}
需要 T: Sync + Send 边界,否则可能通过 Arc 跨线程边界共享线程不安全的值,导致数据竞争或不健全。
例如,若没有这些边界,Arc<Rc<u32>> 会是 Sync 或 Send,意味着可以把 Rc 从 Arc 里 clone 出来跨线程发送(而不创建全新的 Rc),而 Rc 不是线程安全的,会产生数据竞争。
获取 ArcInner
要把 NonNull<T> 解引用为 &T,可调用 NonNull::as_ref。这与典型的 as_ref 不同,是不安全的,必须这样写:
unsafe { self.ptr.as_ref() }
这段代码会多次出现(通常配合 let 绑定)。
这种不安全是合理的:只要这个 Arc 还活着,内部指针就有效。
Deref
好。现在能构造 Arc(很快也能正确 clone 和销毁),如何访问内部数据?
需要实现 Deref。
先导入 trait:
use std::ops::Deref;
实现如下:
impl<T> Deref for Arc<T> {
type Target = T;
fn deref(&self) -> &T {
let inner = unsafe { self.ptr.as_ref() };
&inner.data
}
}
很简单:解引用 NonNull 到 ArcInner<T>,再取内部数据的引用。
代码
本节全部代码:
use std::ops::Deref;
impl<T> Arc<T> {
pub fn new(data: T) -> Arc<T> {
// 引用计数从 1 开始,因为第一个引用就是当前指针。
let boxed = Box::new(ArcInner {
rc: AtomicUsize::new(1),
data,
});
Arc {
// 这里调用 `.unwrap()` 没问题,因为 `Box::into_raw`
// 返回的指针保证非 null。
ptr: NonNull::new(Box::into_raw(boxed)).unwrap(),
phantom: PhantomData,
}
}
}
unsafe impl<T: Sync + Send> Send for Arc<T> {}
unsafe impl<T: Sync + Send> Sync for Arc<T> {}
impl<T> Deref for Arc<T> {
type Target = T;
fn deref(&self) -> &T {
let inner = unsafe { self.ptr.as_ref() };
&inner.data
}
}