main.rs (5653B)
1 use std::{ 2 cell::UnsafeCell, 3 hint::spin_loop, 4 ops::{Deref, DerefMut}, 5 sync::atomic::{ 6 AtomicBool, 7 Ordering::{Acquire, Relaxed, Release}, 8 }, 9 thread, 10 }; 11 12 /// A Spinlock is the simplest possible implementation of a mutex, its general form looks like this 13 /// ```rust 14 /// static LOCKED: AtomicBool = AtomicBool::new(false); 15 /// // 1. To grab a lock, we repeatedly execute compareandswap until it succeeds. 16 /// // The CPU “spins” in this very short loop. 17 /// while LOCKED.compare_and_swap(false, true, Ordering::Acquire) { 18 /// // 4. Spinning is wasteful, so we use an intrinsic to instruct the CPU to 19 /// // enter a low-power mode. 20 /// std::sync::atomic::spin_loop_hint(); 21 /// } 22 /// // 2. Only one thread at a time can be here. 23 /// /* Critical section */ 24 /// // 3. To release the lock, we do a single atomic store. 25 /// LOCKED.store(false, Ordering::Release); 26 /// ``` 27 /// Checkout the "Spinlock Considered Harmful" post: 28 /// https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html 29 /// 30 /// All we need is a single boolean that indicates whether it is locked or not. 31 pub struct SpinLock<T> { 32 /// QUESTION: What does an atomic bool does conceptually? 33 /// - Stores a true/false value that can be shared across threads without a mutex. 34 /// - Provides atomic operations like load, store, swap, compare_exchange, 35 /// and bitwise ops (fetch_or, fetch_and, fetch_not), each taking a memory 36 /// Ordering to control how operations are seen across threads. 37 /// - Uses CPU atomic instructions so an update is either fully seen or not seen at all by other threads; there is no partial write. 38 locked: AtomicBool, 39 40 /// We need to have an exclusive reference (&mut T) to the data protected by the lock 41 /// The value field holds the generic over the type of data the lock protects 42 /// We use UnsafeCell for interior mutability 43 value: UnsafeCell<T>, 44 } 45 46 /// A Safe Interface Using a Lock Guard 47 /// 48 /// Wrap the reference in a type that implements the Drop trait to do something 49 /// when it is dropped. 50 /// 51 /// The existence of a Guard means that the SpinLock has been locked. 52 pub struct Guard<'a, T> { 53 lock: &'a SpinLock<T>, 54 } 55 56 /// In order to make the UnsafeCell to be shareable between threads, we need to 57 /// promise to the compiler that it is actually safe for our type to be shared 58 /// between threads. 59 unsafe impl<T> Sync for SpinLock<T> where T: Send {} 60 61 /// Mutual Exclusion — the guarantee that only one thread can access the 62 /// protected data at any given moment. 63 /// 64 /// Spinlock Mechanism 65 impl<T> SpinLock<T> { 66 pub const fn new(value: T) -> Self { 67 Self { 68 locked: AtomicBool::new(false), 69 value: UnsafeCell::new(value), 70 } 71 } 72 73 /// The lock method returns a Guard, such that the user isn't required to 74 /// write unsafe, unchecked code when using the lock to protect their data 75 pub fn lock(&self) -> Guard<'_, T> { 76 while self.locked.swap(true, Acquire) { 77 // Within the while loop, we use a spin loop hint, which emits a 78 // special CPU instruction that says “I’m in a tight busy‑wait loop; 79 // expect lots of repeated reads and no useful work.” This lets 80 // the core change how it treats that thread without involving 81 // the OS scheduler. 82 spin_loop(); 83 } 84 Guard { lock: self } 85 } 86 87 /// We use acquire and release memory ordering to make sure that every 88 /// unlock() call establishes a happens-before relationship with the 89 /// lock() calls that follow. 90 /// 91 /// # Safety 92 /// 93 /// The &mut T from lock() must be gone! 94 /// (And no cheating by keeping reference to fields of that T around!) 95 pub unsafe fn unlock(&self) { 96 self.locked.store(false, Release); 97 } 98 99 /// use a compare-and-exchange operation to atomically check if the boolean 100 /// is false and set it to true if that’s the case 101 pub fn cas(&self) { 102 while self 103 .locked 104 .compare_exchange_weak(false, true, Acquire, Relaxed) 105 .is_err() 106 { 107 spin_loop(); 108 } 109 } 110 } 111 112 /// To make Guard<T> behave like an (exclusive) reference, we have to implement 113 /// the special Deref and DerefMut traits 114 impl<T> Deref for Guard<'_, T> { 115 type Target = T; 116 fn deref(&self) -> &T { 117 // # Safety: 118 // The very existence of this Guard 119 // guarantees we've exclusively locked the lock. 120 unsafe { &*self.lock.value.get() } 121 } 122 } 123 124 impl<T> DerefMut for Guard<'_, T> { 125 fn deref_mut(&mut self) -> &mut T { 126 // # Safety: 127 // The very existence of this Guard 128 // guarantees we've exclusively locked the lock. 129 unsafe { &mut *self.lock.value.get() } 130 } 131 } 132 133 /// Add our own implementation of Send and Sync with the right bounds to make sure 134 /// our Guard is only Sync if T is Sync (and Send if T is Send) 135 unsafe impl<T> Send for Guard<'_, T> where T: Send {} 136 unsafe impl<T> Sync for Guard<'_, T> where T: Sync {} 137 138 /// Implement Drop for Guard, allowing us to the unsafe unlock method safe again 139 impl<T> Drop for Guard<'_, T> { 140 fn drop(&mut self) { 141 self.lock.locked.store(false, Release); 142 } 143 } 144 145 fn main() { 146 let x = SpinLock::new(Vec::new()); 147 thread::scope(|s| { 148 s.spawn(|| x.lock().push(1)); 149 s.spawn(|| { 150 let mut g = x.lock(); 151 g.push(2); 152 g.push(2); 153 }); 154 }); 155 let g = x.lock(); 156 assert!(g.as_slice() == [1, 2, 2] || g.as_slice() == [2, 2, 1]); 157 println!("{:#?}", g.as_slice()); 158 }