外壳与守卫
主线位置:主线 1 的两端。
parking_lot::Mutex::lock()实际调用的是lock_api::Mutex::lock:self.raw.lock()之后造一个守卫;守卫析构时self.mutex.raw.unlock()。中间的停车、交接都发生在raw里,外壳只负责「数据放哪、谁能摸到、什么时候解锁」。
flowchart LR m["Mutex<R, T>"] -- "lock()" --> g["MutexGuard<'a, R, T><br/>&'a Mutex"] m -- "lock_arc()(arc_lock)" --> ag["ArcMutexGuard<R, T><br/>Arc<Mutex>"] g -- "map / try_map / try_map_or_err" --> mg["MappedMutexGuard<'a, R, U><br/>&'a R + *mut U"] mg -- "map" --> mg g -- "drop" --> u["raw.unlock()"] mg -- "drop" --> u ag -- "drop / into_arc" --> u g -- "unlocked(f)" --> rl["unlock → f() → lock"]
Mutex 只有两个字段
lock_api/src/mutex.rs 第 138–144 行:
pub struct Mutex<R, T: ?Sized> {
raw: R,
data: UnsafeCell<T>,
}
unsafe impl<R: RawMutex + Send, T: ?Sized + Send> Send for Mutex<R, T> {}
unsafe impl<R: RawMutex + Sync, T: ?Sized + Send> Sync for Mutex<R, T> {}
没有毒化标志,没有等待队列,没有指针。Mutex<R, T> 的大小就是 R 加 T 再按对齐补齐:parking_lot 的 RawMutex 是一个 AtomicU8,所以 Mutex<()> 是 1 字节、Mutex<u64> 是 16 字节(E1 的表在 和 std Mutex 比)。本卷的 custom-lock 实验里,文档示例的自旋锁和 spin 0.9.9 的 lock_api::Mutex 也是 1 / 16 字节。
Sync 只要求 T: Send,和 std Mutex 一样:同一时刻只有一个线程能摸到 T,所以 T 只需要能被「移交」给别的线程,不需要能被并发共享。
构造与拆解:
| 方法 | 位置 | 说明 |
|---|---|---|
const fn new(val) | 第 146–161 行 | raw: R::INIT,能写在 static 里 |
const fn from_raw(raw, val) | 第 163–182 行 | 用现成的 raw 锁构造;不要求 R: RawMutex。lock_api 0.4.12 加(#429,提交 8c7aef2);const_new 是它的旧名 |
into_inner / get_mut | 第 156–160、236–243 行 | 拿到所有权或 &mut self 时不需要加锁 |
data_ptr | 第 283–297 行 | 安全函数,返回 *mut T;解引用才 unsafe。lock_api 0.4.1 加(#247) |
unsafe raw() | 第 269–281 行 | 拿到 &R;unsafe 是因为拿到之后可以在守卫还活着时解锁 |
unsafe force_unlock | 第 252–267 行 | 配合 mem::forget(guard) 用,例如跨 FFI 持锁 |
unsafe make_guard_unchecked | 第 184–199 行 | 凭空造守卫;lock_api 0.4.11 加(#403) |
lock 本身只有两行(第 201–216 行):self.raw.lock(),然后 unsafe { self.make_guard_unchecked() }。文档写「Attempts to lock a mutex in the thread which already holds the lock will result in a deadlock」,外壳不做重入检查。
MutexGuard 只存一个引用
#[clippy::has_significant_drop]
#[must_use = "if unused the Mutex will immediately unlock"]
pub struct MutexGuard<'a, R: RawMutex, T: ?Sized> {
mutex: &'a Mutex<R, T>,
marker: PhantomData<(&'a mut T, R::GuardMarker)>,
}
守卫 8 字节,就是一个指针。PhantomData 里的 &'a mut T 让守卫在变型和自动 trait 上表现得像一个 &mut T,R::GuardMarker 决定它是不是 Send(下一章)。Deref / DerefMut 都是 unsafe { &*self.mutex.data.get() },Drop 是 self.mutex.raw.unlock()(第 686–709 行)。
#[must_use] 防的是 mutex.lock(); 这种写法:守卫立即析构,锁立即释放。#[clippy::has_significant_drop] 让 clippy 的 significant_drop_in_scrutinee 一类 lint 认出这个类型的析构有副作用,例如 match m.lock().x { .. } 里守卫会活到整个 match 结束。这个属性是 lock_api 0.4.10 加的(CHANGELOG「Mark guards with #[clippy::has_significant_drop] (#369, #371)」,提交 59dfa3d 先给 MutexGuard,a87498c 再推广到所有守卫)。
守卫上的方法都是关联函数(MutexGuard::map(g, f),不是 g.map(f)),文档的理由是「A method would interfere with methods of the same name on the contents of the locked data」:守卫 Deref 到 T,T 自己有 map 方法时,方法调用语法会产生歧义。
| 关联函数 | 位置 | 做什么 |
|---|---|---|
mutex(&g) | 第 511–515 行 | 拿回原来的 &Mutex |
map(g, f) | 第 517–538 行 | f: FnOnce(&mut T) -> &mut U,得到 MappedMutexGuard |
try_map(g, f) | 第 540–565 行 | f 返回 None 时把原守卫还回来 |
try_map_or_err(g, f) | 第 567–596 行 | f 返回 Err(e) 时还回 (原守卫, e);lock_api 0.4.14 加(#480,提交 f10e168) |
unlocked(&mut g, f) | 第 598–614 行 | 临时解锁执行 f,再加锁 |
leak(g) | 第 616–625 行 | 返回 &'a mut T,锁永远不释放 |
unlock_fair / unlocked_fair / bump | 第 628–684 行 | 只在 R: RawMutexFair 时存在(公平解锁、bump 与 FairMutex) |
unlocked 的写法:
pub fn unlocked<F, U>(s: &mut Self, f: F) -> U
where
F: FnOnce() -> U,
{
// Safety: A MutexGuard always holds the lock.
unsafe {
s.mutex.raw.unlock();
}
defer!(s.mutex.raw.lock());
f()
}
defer! 来自 scopeguard(lock_api 唯一的必需依赖):f 正常返回或 panic 展开时都会重新加锁,守卫的析构才能继续成立「此时持有锁」这个前提。参数是 &mut Self,所以 f 执行期间调用方拿不到 &T,也就不会在没锁的时候读数据。
map 之后不能 unlocked / bump
/// The main difference between `MappedMutexGuard` and `MutexGuard` is that the
/// former doesn't support temporarily unlocking and re-locking, since that
/// could introduce soundness issues if the locked object is modified by another
/// thread.
pub struct MappedMutexGuard<'a, R: RawMutex, T: ?Sized> {
raw: &'a R,
data: *mut T,
marker: PhantomData<&'a mut T>,
}
map 时把 &s.mutex.raw 和 f 返回的指针存下来,然后 mem::forget(s),原守卫不析构,锁的所有权转给映射后的守卫(第 526–538 行)。映射后的守卫只剩 &R,数据指针指向 T 内部的某个位置,例如 Vec 的某个元素。
文档给出的理由是数据可能在解锁期间被别人改掉:unlocked 期间另一个线程拿到锁,把那个 Vec 清空或重新分配,映射守卫里的 *mut U 就指向已经不属于这个 Vec 的内存,重新加锁也无法让它变回有效。MutexGuard 没有这个问题,它每次 Deref 都从 Mutex 重新取 data.get()。bump 等价于「公平解锁再加锁」,同理不提供。unlock_fair 只解锁、不再访问数据,所以映射守卫上仍然有(第 975–997 行)。
映射守卫的大小是 &R 加一个可能是胖指针的 *mut T:custom-lock 实验里 MappedMutexGuard<u64> 16 字节、MappedMutexGuard<[u8]> 24 字节,MutexGuard<u64> 8 字节。
ArcMutexGuard:不借用的守卫
开 arc_lock feature 后,Mutex 在 self: &Arc<Self> 上多出 lock_arc / try_lock_arc(第 316–344 行),R: RawMutexTimed 时还有 try_lock_arc_for / try_lock_arc_until(第 402–431 行)。守卫(第 726–736 行):
/// This is similar to the `MutexGuard` struct, except instead of using a reference to unlock the `Mutex` it
/// uses an `Arc<Mutex>`. This has several advantages, most notably that it has an `'static` lifetime.
pub struct ArcMutexGuard<R: RawMutex, T: ?Sized> {
mutex: Arc<Mutex<R, T>>,
marker: PhantomData<*const ()>,
}
它持有一份 Arc 的引用计数,没有生命周期参数,可以放进结构体字段、从函数里返回、放进 'static 的闭包。Send / Sync 不再由 PhantomData 自动推出,而是在第 738–747 行手写,条件里同样有 R::GuardMarker: Send。
into_arc(g)(第 758–768 行):解锁并把Arc交还,不多做一次引用计数的增减。lock_api 0.4.8 加(#350);公平版本into_arc_fair和 RwLock 各守卫上的同名方法在 0.4.14 补齐(「Added missinginto_arc(_fair)methods (#472)」)。- 没有
map:lock_api 0.4.14 没有「映射后的 Arc 守卫」类型。
parking_lot 0.12.3 起从顶层重导出这几个类型(src/lib.rs 第 58–62 行,CHANGELOG「Export types provided by arc_lock feature (#442)」)。
RwLock 的 8 种守卫
lock_api::RwLock<R, T> 的字段和 Mutex 一样是 raw + UnsafeCell<T>(rwlock.rs 第 318–333 行)。Sync 要求 T: Send + Sync(第 365 行),因为多个读者会同时拿到 &T。
| 守卫 | 借用 / Arc | 能 map | 特有的关联函数 |
|---|---|---|---|
RwLockReadGuard | &'a RwLock | 是 → MappedRwLockReadGuard | unlocked;R: RawRwLockFair 时 unlock_fair / bump |
RwLockWriteGuard | &'a RwLock | 是 → MappedRwLockWriteGuard | downgrade(RawRwLockDowngrade)、downgrade_to_upgradable(RawRwLockUpgradeDowngrade) |
RwLockUpgradableReadGuard | &'a RwLock | 否 | upgrade、try_upgrade、try_upgrade_for / until、downgrade、with_upgraded 系列 |
ArcRwLockReadGuard | Arc<RwLock> | 否 | into_arc、unlocked |
ArcRwLockWriteGuard | Arc<RwLock> | 否 | downgrade、downgrade_to_upgradable |
ArcRwLockUpgradableReadGuard | Arc<RwLock> | 否 | 同普通可升级守卫 |
MappedRwLockReadGuard | &'a R + 指针 | 是 | 无 unlocked;第三个映射函数叫 try_map_or_else(其他守卫上叫 try_map_or_err,0.13 统一改名,#533 中的 9929a69) |
MappedRwLockWriteGuard | &'a R + 指针 | 是 | 无 unlocked、无 downgrade |
MappedRwLockWriteGuard::downgrade 以前有过,0.11.0 删掉了,CHANGELOG 原文「Remove deprecated and unsound MappedRwLockWriteGuard::downgrade. (#244)」。
可升级守卫上的 upgrade 系列消费守卫、返回写守卫;with_upgraded(第 2207–2220 行)则借用守卫,临时升级执行闭包,再用 defer! 降回可升级读:
pub fn with_upgraded<Ret, F: FnOnce(&mut T) -> Ret>(&mut self, f: F) -> Ret {
unsafe {
self.rwlock.raw.upgrade();
}
// Safety: We just upgraded the lock, so we have mutable access to the data.
// This will restore the state the lock was in at the start of the function.
defer!(unsafe { self.rwlock.raw.downgrade_to_upgradable() });
// Safety: We upgraded the lock, so we have mutable access to the data.
// When this function returns, whether by drop or panic,
// the drop guard will downgrade it back to an upgradeable lock.
f(unsafe { &mut *self.rwlock.data.get() })
}
这组 API 在 lock_api 0.4.10 加入(#386),之后两次修复:0.4.11 的 #393、0.4.12 的 #431(「Fixed panic when calling with_upgraded twice on a ArcRwLockUpgradableReadGuard」)。升级、降级在 parking_lot 状态字上的实现见 可升级读、降级与 with_upgraded。
ReentrantMutexGuard 只给 &T(Deref,没有 DerefMut),因为同一线程可能同时持有多个守卫;它的 map 得到 MappedReentrantMutexGuard,同样只读。见 ReentrantMutex。
serde 与 owning_ref
serde feature(第 468–495 行):
impl<R, T> Serialize for Mutex<R, T>
where
R: RawMutex,
T: Serialize + ?Sized,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.lock().serialize(serializer)
}
}
序列化时加锁、序列化 T;输出里只有数据,没有锁的状态。反序列化读出一个 T 再 Mutex::new,得到一把新的、未上锁的锁。由于序列化里调的是普通 lock(),当前线程已经持有这把锁时去序列化它,会和其他同线程重复加锁一样卡住。RwLock 的 Serialize 用 read(),ReentrantMutex 的用可重入的 lock()。parking_lot 的 serde feature 只是转发到 lock_api/serde。
owning_ref feature 给守卫实现 owning_ref::StableAddress(例如第 723–724 行),让守卫可以当 OwningRef 的所有者。这个依赖已经不维护,0.13 仍保留这个 feature。
小结
Mutex<R, T>=raw: R+data: UnsafeCell<T>,大小只比T多一个 raw 锁;new是const fn,from_raw接受现成的 raw 锁。MutexGuard只存&Mutex,8 字节;方法都是关联函数,避免和T的方法撞名;unlocked用defer!保证 panic 时也重新加锁。map后的守卫只剩&R和数据指针,所以不提供unlocked/bump:文档的理由是解锁期间数据可能被别人改掉,映射出来的指针随之失效。ArcMutexGuard持有Arc,没有生命周期参数;into_arc(_fair)在 0.4.14 补齐;没有映射版本。- RwLock 共 8 种守卫;可升级守卫不能
map,with_upgraded借用守卫临时升级,panic 时也会降回来。 serde只序列化数据,反序列化得到新锁;序列化内部会加锁。