exercises

Log | Files | Refs | README

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 }