rustのMutexの内部構造
前回に引き続き,今度はMutexの話です.
Raph Levien氏の図を引用すると,Mutexは以下のような構造になっています.
また,データ構造としては以下のように定義されています(一部抜粋).
pub struct Mutex<T: ?Sized> { inner: Box<sys::Mutex>, poison: poison::Flag, data: UnsafeCell<T>, }
ここから,以下のことが分かります.
Mutex
自体はデータをヒープに置かない- mutex本体(
sys::Mutex
)は,ヒープに置かれる
sys::Mutex
は,*nix環境ではpthread_mutex_t
です.
したがって,Mutexのlock/unlockはpthread_mutex_lock()
およびpthread_mutex_unlock()
に対応しています.
mutex本体をヒープに置くのは,OSによっては(pthreadが?)mutex本体のアドレスが変わらないことを前提としているからだそうです(このあたりの実装は未確認).
ヒープに置いておかないと,Mutexをmoveしたときmutex本体のアドレスが変わってしまいます.
ちなみに,RwLock
もMutex
とほぼ同じ構造をしています.
https://github.com/rust-lang/rust/blob/1.32.0/src/libstd/sync/rwlock.rs
pub struct RwLock<T: ?Sized> { inner: Box<sys::RWLock>, poison: poison::Flag, data: UnsafeCell<T>, }
Mutexを利用するときはマルチスレッドで何かしたい場合なので,実際に利用する場合は,Arcと組み合わせてArc<Mutex<T>>
のような形で利用されることが多いと思います.
parking_lot
さて,上のMutexの図には"parking_lot
を使ってみたら?"と書いてあります.
parking_lot
はスピンロックを使ったmutexを提供します.
以下のような感じでMutex
が定義されます(一部抜粋).
// lock_api/src/mutex.rc pub struct Mutex<R: RawMutex, T: ?Sized> { raw: R, data: UnsafeCell<T>, } // src/mutex.rc type Mutex<T> = lock_api::Mutex<RawMutex, T>; // src/raw_mutex.rs pub struct RawMutex { state: AtomicU8, } unsafe impl RawMutexTrait for RawMutex { ... #[inline] fn lock(&self) { if self .state .compare_exchange_weak(0, LOCKED_BIT, Ordering::Acquire, Ordering::Relaxed) .is_err() { self.lock_slow(None); } unsafe { deadlock::acquire_resource(self as *const _ as usize) }; } ... }
stdのMutexと比較すると,以下の特徴があります.
- pthreadではなくて(adaptive)スピンロックを利用する
- mutex本体をヒープに割り当てる必要がない
- poisonフィールドもない
- このフィールドは,ロックを保持したスレッドがpanicしたかどうか検出するためのもの
parking_lot
の場合,panicしたらlockは解放されるようになっている
さて,こんなparking_lot
ですが,ちょうど今parking_lot
をlibstdに取り込もうという動きがあるようです.
- https://internals.rust-lang.org/t/standard-library-synchronization-primitives-and-undefined-behavior/8439
- https://github.com/rust-lang/rust/pull/56409
深く議論は追っていませんが,しばらくするとstdに追加されてるかもしれません(というか,OS nativeなmutexの代わりにバックエンドとして利用されることになりそう?).