std/sys/sync/mutex/
futex.rs

1use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
2use crate::sys::futex::{self, futex_wait, futex_wake};
3
4type Futex = futex::SmallFutex;
5type State = futex::SmallPrimitive;
6
7pub struct Mutex {
8    futex: Futex,
9}
10
11const UNLOCKED: State = 0;
12const LOCKED: State = 1; // locked, no other threads waiting
13const CONTENDED: State = 2; // locked, and other threads waiting (contended)
14
15impl Mutex {
16    #[inline]
17    pub const fn new() -> Self {
18        Self { futex: Futex::new(UNLOCKED) }
19    }
20
21    #[inline]
22    // Make this a diagnostic item for Miri's concurrency model checker.
23    #[cfg_attr(not(test), rustc_diagnostic_item = "sys_mutex_try_lock")]
24    pub fn try_lock(&self) -> bool {
25        self.futex.compare_exchange(UNLOCKED, LOCKED, Acquire, Relaxed).is_ok()
26    }
27
28    #[inline]
29    // Make this a diagnostic item for Miri's concurrency model checker.
30    #[cfg_attr(not(test), rustc_diagnostic_item = "sys_mutex_lock")]
31    pub fn lock(&self) {
32        if self.futex.compare_exchange(UNLOCKED, LOCKED, Acquire, Relaxed).is_err() {
33            self.lock_contended();
34        }
35    }
36
37    #[cold]
38    fn lock_contended(&self) {
39        // Spin first to speed things up if the lock is released quickly.
40        let mut state = self.spin();
41
42        // If it's unlocked now, attempt to take the lock
43        // without marking it as contended.
44        if state == UNLOCKED {
45            match self.futex.compare_exchange(UNLOCKED, LOCKED, Acquire, Relaxed) {
46                Ok(_) => return, // Locked!
47                Err(s) => state = s,
48            }
49        }
50
51        loop {
52            // Put the lock in contended state.
53            // We avoid an unnecessary write if it as already set to CONTENDED,
54            // to be friendlier for the caches.
55            if state != CONTENDED && self.futex.swap(CONTENDED, Acquire) == UNLOCKED {
56                // We changed it from UNLOCKED to CONTENDED, so we just successfully locked it.
57                return;
58            }
59
60            // Wait for the futex to change state, assuming it is still CONTENDED.
61            futex_wait(&self.futex, CONTENDED, None);
62
63            // Spin again after waking up.
64            state = self.spin();
65        }
66    }
67
68    fn spin(&self) -> State {
69        let mut spin = 100;
70        loop {
71            // We only use `load` (and not `swap` or `compare_exchange`)
72            // while spinning, to be easier on the caches.
73            let state = self.futex.load(Relaxed);
74
75            // We stop spinning when the mutex is UNLOCKED,
76            // but also when it's CONTENDED.
77            if state != LOCKED || spin == 0 {
78                return state;
79            }
80
81            crate::hint::spin_loop();
82            spin -= 1;
83        }
84    }
85
86    #[inline]
87    // Make this a diagnostic item for Miri's concurrency model checker.
88    #[cfg_attr(not(test), rustc_diagnostic_item = "sys_mutex_unlock")]
89    pub unsafe fn unlock(&self) {
90        if self.futex.swap(UNLOCKED, Release) == CONTENDED {
91            // We only wake up one thread. When that thread locks the mutex, it
92            // will mark the mutex as CONTENDED (see lock_contended above),
93            // which makes sure that any other waiting threads will also be
94            // woken up eventually.
95            self.wake();
96        }
97    }
98
99    #[cold]
100    fn wake(&self) {
101        futex_wake(&self.futex);
102    }
103}