1 use crate::sync::atomic::{
3 Ordering::{Acquire, Relaxed, Release},
5 use crate::sys::futex::{futex_wait, futex_wake};
9 /// 1: locked, no other threads waiting
10 /// 2: locked, and other threads waiting (contended)
16 pub const fn new() -> Self {
17 Self { futex: AtomicU32::new(0) }
21 pub fn try_lock(&self) -> bool {
22 self.futex.compare_exchange(0, 1, Acquire, Relaxed).is_ok()
27 if self.futex.compare_exchange(0, 1, Acquire, Relaxed).is_err() {
28 self.lock_contended();
33 fn lock_contended(&self) {
34 // Spin first to speed things up if the lock is released quickly.
35 let mut state = self.spin();
37 // If it's unlocked now, attempt to take the lock
38 // without marking it as contended.
40 match self.futex.compare_exchange(0, 1, Acquire, Relaxed) {
41 Ok(_) => return, // Locked!
47 // Put the lock in contended state.
48 // We avoid an unnecessary write if it as already set to 2,
49 // to be friendlier for the caches.
50 if state != 2 && self.futex.swap(2, Acquire) == 0 {
51 // We changed it from 0 to 2, so we just successfully locked it.
55 // Wait for the futex to change state, assuming it is still 2.
56 futex_wait(&self.futex, 2, None);
58 // Spin again after waking up.
63 fn spin(&self) -> u32 {
66 // We only use `load` (and not `swap` or `compare_exchange`)
67 // while spinning, to be easier on the caches.
68 let state = self.futex.load(Relaxed);
70 // We stop spinning when the mutex is unlocked (0),
71 // but also when it's contended (2).
72 if state != 1 || spin == 0 {
76 crate::hint::spin_loop();
82 pub unsafe fn unlock(&self) {
83 if self.futex.swap(0, Release) == 2 {
84 // We only wake up one thread. When that thread locks the mutex, it
85 // will mark the mutex as contended (2) (see lock_contended above),
86 // which makes sure that any other waiting threads will also be
87 // woken up eventually.
94 futex_wake(&self.futex);