[Day31] Read Rust Atomics and Locks - A safe Spin Lock with a Lock Guard
by Mara Bos
At Topic: Chapter 4. A Safe Interface Using a Lock Guard
Recommendation: Start reading from the beginning of the chapter of the book to get the full picture
Prerequisites
Lifetime Annotations in Struct Definitions
Eg. Struct ImportantExcerpt
has the single field part that holds a string slice, which is a reference. 'a
means an instance of ImportantExcerpt
can’t outlive the reference it holds in its part
field.
struct ImportantExcerpt<'a> { part: &'a str, }
Deref
- Enable dereferencing with the
*
operator - Enable to use the method of the inner type directly on the outer type of struct
DerefMut
is the same asDeref
but for exclusive references
Recall
What is spin lock?
Instead of putting the thread to sleep, the thread will continuously attempt to lock the locked lock
When to use spin lock?
If a lock is only ever held for very brief moments and the threads locking it can run in parallel on different processor cores.
Notes
To be able to provide a fully safe interface, we need to tie the unlocking operation to the end of the &mut T
.
How to do it?
- Wrapping reference of SpinLock in our own type that behaves like a reference. => use type
Guard
+Deref
trait +DerefMut
trait - Implements the
Drop
trait to do something when it is dropped.
// source: https://github.com/m-ou-se/rust-atomics-and-locks/blob/main/src/ch4_spin_lock/s3_guard.rs use std::ops::{Deref, DerefMut}; use std::cell::UnsafeCell; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::{Acquire, Release}; pub struct SpinLock<T> { locked: AtomicBool, value: UnsafeCell<T>, } // implement `Send` trait to make sure that the data can be shared between threads unsafe impl<T> Sync for SpinLock<T> where T: Send {} // it effectively guards the state of the lock, and stays responsible for that state until it is dropped. pub struct Guard<'a, T> { // use ref because we want to be able to access UnsafeCell and AtomicBool lock: &'a SpinLock<T>, } unsafe impl<T> Sync for Guard<'_, T> where T: Sync {} impl<T> SpinLock<T> { pub const fn new(value: T) -> Self { Self { locked: AtomicBool::new(false), value: UnsafeCell::new(value), } } pub fn lock(&self) -> Guard<T> { while self.locked.swap(true, Acquire) { std::hint::spin_loop(); } // Our Guard type has no constructor and its field is private meaning this is the only way the user can obtain a Guard => spin lock has been locked Guard { lock: self } } } impl<T> Deref for Guard<'_, T> { type Target = T; fn deref(&self) -> &T { // Safety: The very existence of this Guard // guarantees we've exclusively locked the lock. unsafe { &*self.lock.value.get() } } } impl<T> DerefMut for Guard<'_, T> { fn deref_mut(&mut self) -> &mut T { // Safety: The very existence of this Guard // guarantees we've exclusively locked the lock. unsafe { &mut *self.lock.value.get() } } } // we do not need unsafe unlock method no more (see day 30) impl<T> Drop for Guard<'_, T> { fn drop(&mut self) { self.lock.locked.store(false, Release); } } #[test] fn main() { use std::thread; let x = SpinLock::new(Vec::new()); thread::scope(|s| { // we can call push method because we implement Deref and DerefMut s.spawn(|| x.lock().push(1)); s.spawn(|| { let mut g = x.lock(); g.push(2); g.push(2); }); }); let g = x.lock(); assert!(g.as_slice() == [1, 2, 2] || g.as_slice() == [2, 2, 1]); }