一个 trait 声明如下:
pub trait SomeTrait: Send{
// ^ super trait
fn some_method(&self);
}Copy
pub trait Copy: Clone { }若一个类型实现了 Copy,则:
此类型可以通过 memcpy 进行拷贝。
此类型是拷贝语义。
由于 Copy 本身是一个 safe trait。因此只有 POD 类型才能实现 Copy trait。
移动语义和拷贝语义都是通过 memmove 实现的,它们唯一的区别在于赋值后是否还能访问被复制的变量。当然,这种操作经常会被优化掉。 |
要实现 Copy 可以使用 derive 的方式或者是手动实现,但是它们略有不同:
#[derive(Clone)]
struct MyStruct<T>(T);
// same as struct MyStruct<T: Copy>(T);
impl<T: Copy> Copy for MyStruct<T> { }derive 会对类型 T 也施加 Copy 限制。而 impl 的形式则允许 T 没有实现 Copy。
若一个类型的所有成员都实现了 Copy,则这个类型可以实现 Copy。对于引用类型而言,&T 实现了 Copy,但是 &mut T 没有实现 Copy。
实现了 Drop 的类型不能实现 Copy。
除了上面的情况外,下面类型也实现了 Copy:
函数项(Function item)类型。
函数指针类型(
fn() → i32)闭包类型。若没有捕获值,或者捕获的值都实现了 Copy,则闭包类型也会实现 Copy。
Send 和 Sync
pub unsafe auto trait Send { }
pub unsafe auto trait Sync { }Send 和 Sync 是两个 unsafe 的 auto 类型。这意味着:
若类型的所有成员都实现了 Send,则此成员自动实现 Send。
手动实现 Send 是一个 unsafe 行为。
如果不需要编译器自动实现的 Send/Sync,则可以手动取消:
#![feature(negative_impls)]
// I have some magic semantics for some synchronization primitive!
struct SpecialThreadToken(u8);
impl !Send for SpecialThreadToken {}
impl !Sync for SpecialThreadToken {}若类型实现了 Send,则可以安全地将其移动到另一个线程。
若类型实现了 Sync,则可以安全地在多个线程之间共享。(当且仅当
&T是 Send 时类型T才是 Sync)
Rc 没有实现 Send,因为引用计数是共享的,且没有同步。因此你无法将一个 Rc 值移动到另一个线程: 同样类似的还有 MutexGuard,这是因为在一个线程获取锁,在另一个线程释放是未定义行为。结合 async 来看,就是 MutexGuard 无法跨越 await 点。 这里注意 parking_lot 为 MutexGuard 实现了 Send,这可能会导致未定义行为。 |
若类型 T 没有实现 Sync,则 &T 无法在多个线程之间共享。UnsafeCell/Cell/RefCell 由于可以通过 &T 修改变量,因此没有实现 Sync。
常见同步原语
Arc
Arc(Atomic Ref Counter) 要求类型实现 Send 和 Sync:
unsafe impl<T: Sync + Send> Send for Arc<T> {}
unsafe impl<T: Sync + Send> Sync for Arc<T> {}Arc 要求类型 T 本身一定已经是线程安全了的。Arc 只是允许在多个线程之间共享值。
考虑 Arc<RefCell<T>> 由于 RefCell<T> 没有实现 Sync,因此 Arc 也不会实现 Sync。
Mutex
Mutex 使用互斥原语保护共享数据:
unsafe impl<T: Send> Send for Mutex<T> {}
unsafe impl<T: Send> Sync for Mutex<T> {}Mutex 可以将一个实现了 Send 的值转为 Send + Sync。
考虑 Mutex<Rc<T>> 由于 Rc 本身没有实现 Send,可能出现下面的情况:
use std::{rc::Rc, sync::Mutex};
fn main() {
let rc = Rc::new(42);
let mrc = Mutex::new(rc.clone());
std::thread::spawn(|| {
println!("{}", mrc.lock().unwrap());
// ^ Mutex<Rc<T>> 未实现 Send。
});
}Mutex 和 MutexGuard 一起使用:
impl<T> !Send for MutexGuard<'_, T> {}
unsafe impl<T: Sync> Sync for MutexGuard<'_, T> {}MutexGuard 未实现 Send,这意味着 MutexGuard 无法发送到另一个线程。这是因为在一个线程加锁,另一个线程解锁是未定义行为。
MutexGuard 实现了 Sync,这是因为 &T 可以被其它线程读取。
parking_lot 的 MutexGuard 没有实现 !Send,因此允许跨线程使用。在 async 的情况下允许持锁跨越 await 点,这本身是不安全的。 |
RwLock
读写锁允许细化锁的使用:
unsafe impl<T: Send> Send for RwLock<T> {}
unsafe impl<T: Send + Sync> Sync for RwLock<T> {}由于 RwLock 允许多个线程读和单个线程写。其中 Send 保证值可以在多个线程之间转移,而 Sync 则保证多个线程可以同时读。
RwLockReadGuard 和 RwLockWriteGuard 和 MutexGuard 的约束相同。
内部可变性
除了常见的锁外,还有一些内部可变性用于单线程操作:UnsafeCell, Cell 和 RefCell。
UnsafeCell
#[repr(transparent)]
pub struct UnsafeCell<T: ?Sized> {
value: T,
}由于施加了 |
UnsafeCell 本身没有做任何工作,它的可变性基于一个 unsafe 操作:
pub const fn get(&self) -> *mut T {
self as *const UnsafeCell<T> as *const T as *mut T
}当然,这个函数是 safe 的,但是解引用是 unsafe 的。
Cell
pub struct Cell<T: ?Sized> {
value: UnsafeCell<T>,
}Cell 通过 std::mem::replace 实现内部可变性。这依赖于下面几个方法:
impl<T> for Cell<T> {
pub fn set(&self, val: T) {
self.replace(val);
}
pub const fn replace(&self, val: T) -> T {
// SAFETY: This can cause data races if called from a separate thread,
// but `Cell` is `!Sync` so this won't happen.
mem::replace(unsafe { &mut *self.value.get() }, val)
}
}
impl<T: Copy> for Cell<T> {
pub const fn get(&self) -> T {
// SAFETY: This can cause data races if called from a separate thread,
// but `Cell` is `!Sync` so this won't happen.
unsafe { *self.value.get() }
}
}对于实现了 Default trait 的类型,还允许通过 take 获取值:
impl<T: Default> for Cell<T> {
pub fn take(&self) -> T {
self.replace(Default::default())
}
}根据上面我们可以认为 Cell 的开销只是一次内存拷贝。
RefCell
RefCell 实现了运行时借用规则:
type BorrowFlag = isize;
pub struct RefCell<T: ?Sized> {
borrow: Cell<BorrowFlag>,
value: UnsafeCell<T>,
}在访问值的时候 RefCell 依赖于 borrow 和 borrow_mut。若在 borrow 的情况下获取 borrow_mut,则会导致程序 panic:
impl<T> for RefCell<T> {
pub fn borrow(&self) -> Ref<'_, T> {
match self.try_borrow() {
Ok(b) => b,
Err(err) => panic_already_mutably_borrowed(err),
}
}
pub fn borrow_mut(&self) -> RefMut<'_, T> {
match self.try_borrow_mut() {
Ok(b) => b,
Err(err) => panic_already_borrowed(err),
}
}
}RefCell 对应的多线程版本是 RwLock。由于 BorrowFlag 只是一个 isize, 因此 RefCell 的开销也很小。
OnceCell
OnceCell 只允许写一次值:
pub struct OnceCell<T> {
// Invariant: written to at most once.
inner: UnsafeCell<Option<T>>,
}
impl<T> for OnceCell<T> {
pub fn set(&self, value: T) -> Result<(), T> {
match self.try_insert(value) {
Ok(_) => Ok(()),
// 若已经有值了,就见 set 提供的 value 返回。
Err((_, value)) => Err(value),
}
}
}OnceCell 的多线程版本是 OnceLock。
LazyCell
LazyCell 在创建时提供一个初始化函数,在访问时无值才会进行初始化:
enum State<T, F> {
Uninit(F),
Init(T),
Poisoned,
}
pub struct LazyCell<T, F = fn() -> T> {
state: UnsafeCell<State<T, F>>,
}当 LazyCell drop 时会将 state 转为 Poisoned。
LazyCell 的多线程版本是 LazyLock。