| use crate::primitive::hint; |
| use core::cell::Cell; |
| use core::fmt; |
| |
| const SPIN_LIMIT: u32 = 6; |
| const YIELD_LIMIT: u32 = 10; |
| |
| /// Performs exponential backoff in spin loops. |
| /// |
| /// Backing off in spin loops reduces contention and improves overall performance. |
| /// |
| /// This primitive can execute *YIELD* and *PAUSE* instructions, yield the current thread to the OS |
| /// scheduler, and tell when is a good time to block the thread using a different synchronization |
| /// mechanism. Each step of the back off procedure takes roughly twice as long as the previous |
| /// step. |
| /// |
| /// # Examples |
| /// |
| /// Backing off in a lock-free loop: |
| /// |
| /// ``` |
| /// use crossbeam_utils::Backoff; |
| /// use std::sync::atomic::AtomicUsize; |
| /// use std::sync::atomic::Ordering::SeqCst; |
| /// |
| /// fn fetch_mul(a: &AtomicUsize, b: usize) -> usize { |
| /// let backoff = Backoff::new(); |
| /// loop { |
| /// let val = a.load(SeqCst); |
| /// if a.compare_exchange(val, val.wrapping_mul(b), SeqCst, SeqCst).is_ok() { |
| /// return val; |
| /// } |
| /// backoff.spin(); |
| /// } |
| /// } |
| /// ``` |
| /// |
| /// Waiting for an [`AtomicBool`] to become `true`: |
| /// |
| /// ``` |
| /// use crossbeam_utils::Backoff; |
| /// use std::sync::atomic::AtomicBool; |
| /// use std::sync::atomic::Ordering::SeqCst; |
| /// |
| /// fn spin_wait(ready: &AtomicBool) { |
| /// let backoff = Backoff::new(); |
| /// while !ready.load(SeqCst) { |
| /// backoff.snooze(); |
| /// } |
| /// } |
| /// ``` |
| /// |
| /// Waiting for an [`AtomicBool`] to become `true` and parking the thread after a long wait. |
| /// Note that whoever sets the atomic variable to `true` must notify the parked thread by calling |
| /// [`unpark()`]: |
| /// |
| /// ``` |
| /// use crossbeam_utils::Backoff; |
| /// use std::sync::atomic::AtomicBool; |
| /// use std::sync::atomic::Ordering::SeqCst; |
| /// use std::thread; |
| /// |
| /// fn blocking_wait(ready: &AtomicBool) { |
| /// let backoff = Backoff::new(); |
| /// while !ready.load(SeqCst) { |
| /// if backoff.is_completed() { |
| /// thread::park(); |
| /// } else { |
| /// backoff.snooze(); |
| /// } |
| /// } |
| /// } |
| /// ``` |
| /// |
| /// [`is_completed`]: Backoff::is_completed |
| /// [`std::thread::park()`]: std::thread::park |
| /// [`Condvar`]: std::sync::Condvar |
| /// [`AtomicBool`]: std::sync::atomic::AtomicBool |
| /// [`unpark()`]: std::thread::Thread::unpark |
| pub struct Backoff { |
| step: Cell<u32>, |
| } |
| |
| impl Backoff { |
| /// Creates a new `Backoff`. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use crossbeam_utils::Backoff; |
| /// |
| /// let backoff = Backoff::new(); |
| /// ``` |
| #[inline] |
| pub fn new() -> Self { |
| Backoff { step: Cell::new(0) } |
| } |
| |
| /// Resets the `Backoff`. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use crossbeam_utils::Backoff; |
| /// |
| /// let backoff = Backoff::new(); |
| /// backoff.reset(); |
| /// ``` |
| #[inline] |
| pub fn reset(&self) { |
| self.step.set(0); |
| } |
| |
| /// Backs off in a lock-free loop. |
| /// |
| /// This method should be used when we need to retry an operation because another thread made |
| /// progress. |
| /// |
| /// The processor may yield using the *YIELD* or *PAUSE* instruction. |
| /// |
| /// # Examples |
| /// |
| /// Backing off in a lock-free loop: |
| /// |
| /// ``` |
| /// use crossbeam_utils::Backoff; |
| /// use std::sync::atomic::AtomicUsize; |
| /// use std::sync::atomic::Ordering::SeqCst; |
| /// |
| /// fn fetch_mul(a: &AtomicUsize, b: usize) -> usize { |
| /// let backoff = Backoff::new(); |
| /// loop { |
| /// let val = a.load(SeqCst); |
| /// if a.compare_exchange(val, val.wrapping_mul(b), SeqCst, SeqCst).is_ok() { |
| /// return val; |
| /// } |
| /// backoff.spin(); |
| /// } |
| /// } |
| /// |
| /// let a = AtomicUsize::new(7); |
| /// assert_eq!(fetch_mul(&a, 8), 7); |
| /// assert_eq!(a.load(SeqCst), 56); |
| /// ``` |
| #[inline] |
| pub fn spin(&self) { |
| for _ in 0..1 << self.step.get().min(SPIN_LIMIT) { |
| hint::spin_loop(); |
| } |
| |
| if self.step.get() <= SPIN_LIMIT { |
| self.step.set(self.step.get() + 1); |
| } |
| } |
| |
| /// Backs off in a blocking loop. |
| /// |
| /// This method should be used when we need to wait for another thread to make progress. |
| /// |
| /// The processor may yield using the *YIELD* or *PAUSE* instruction and the current thread |
| /// may yield by giving up a timeslice to the OS scheduler. |
| /// |
| /// In `#[no_std]` environments, this method is equivalent to [`spin`]. |
| /// |
| /// If possible, use [`is_completed`] to check when it is advised to stop using backoff and |
| /// block the current thread using a different synchronization mechanism instead. |
| /// |
| /// [`spin`]: Backoff::spin |
| /// [`is_completed`]: Backoff::is_completed |
| /// |
| /// # Examples |
| /// |
| /// Waiting for an [`AtomicBool`] to become `true`: |
| /// |
| /// ``` |
| /// use crossbeam_utils::Backoff; |
| /// use std::sync::Arc; |
| /// use std::sync::atomic::AtomicBool; |
| /// use std::sync::atomic::Ordering::SeqCst; |
| /// use std::thread; |
| /// use std::time::Duration; |
| /// |
| /// fn spin_wait(ready: &AtomicBool) { |
| /// let backoff = Backoff::new(); |
| /// while !ready.load(SeqCst) { |
| /// backoff.snooze(); |
| /// } |
| /// } |
| /// |
| /// let ready = Arc::new(AtomicBool::new(false)); |
| /// let ready2 = ready.clone(); |
| /// |
| /// thread::spawn(move || { |
| /// thread::sleep(Duration::from_millis(100)); |
| /// ready2.store(true, SeqCst); |
| /// }); |
| /// |
| /// assert_eq!(ready.load(SeqCst), false); |
| /// spin_wait(&ready); |
| /// assert_eq!(ready.load(SeqCst), true); |
| /// # std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 |
| /// ``` |
| /// |
| /// [`AtomicBool`]: std::sync::atomic::AtomicBool |
| #[inline] |
| pub fn snooze(&self) { |
| if self.step.get() <= SPIN_LIMIT { |
| for _ in 0..1 << self.step.get() { |
| hint::spin_loop(); |
| } |
| } else { |
| #[cfg(not(feature = "std"))] |
| for _ in 0..1 << self.step.get() { |
| hint::spin_loop(); |
| } |
| |
| #[cfg(feature = "std")] |
| ::std::thread::yield_now(); |
| } |
| |
| if self.step.get() <= YIELD_LIMIT { |
| self.step.set(self.step.get() + 1); |
| } |
| } |
| |
| /// Returns `true` if exponential backoff has completed and blocking the thread is advised. |
| /// |
| /// # Examples |
| /// |
| /// Waiting for an [`AtomicBool`] to become `true` and parking the thread after a long wait: |
| /// |
| /// ``` |
| /// use crossbeam_utils::Backoff; |
| /// use std::sync::Arc; |
| /// use std::sync::atomic::AtomicBool; |
| /// use std::sync::atomic::Ordering::SeqCst; |
| /// use std::thread; |
| /// use std::time::Duration; |
| /// |
| /// fn blocking_wait(ready: &AtomicBool) { |
| /// let backoff = Backoff::new(); |
| /// while !ready.load(SeqCst) { |
| /// if backoff.is_completed() { |
| /// thread::park(); |
| /// } else { |
| /// backoff.snooze(); |
| /// } |
| /// } |
| /// } |
| /// |
| /// let ready = Arc::new(AtomicBool::new(false)); |
| /// let ready2 = ready.clone(); |
| /// let waiter = thread::current(); |
| /// |
| /// thread::spawn(move || { |
| /// thread::sleep(Duration::from_millis(100)); |
| /// ready2.store(true, SeqCst); |
| /// waiter.unpark(); |
| /// }); |
| /// |
| /// assert_eq!(ready.load(SeqCst), false); |
| /// blocking_wait(&ready); |
| /// assert_eq!(ready.load(SeqCst), true); |
| /// # std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 |
| /// ``` |
| /// |
| /// [`AtomicBool`]: std::sync::atomic::AtomicBool |
| #[inline] |
| pub fn is_completed(&self) -> bool { |
| self.step.get() > YIELD_LIMIT |
| } |
| } |
| |
| impl fmt::Debug for Backoff { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| f.debug_struct("Backoff") |
| .field("step", &self.step) |
| .field("is_completed", &self.is_completed()) |
| .finish() |
| } |
| } |
| |
| impl Default for Backoff { |
| fn default() -> Backoff { |
| Backoff::new() |
| } |
| } |