//! Code that decides when workers should go to sleep. See README.md //! for an overview.
usecrate::latch::CoreLatch; usecrate::sync::{Condvar, Mutex}; use crossbeam_utils::CachePadded; use std::sync::atomic::Ordering; use std::thread; use std::usize;
mod counters; pub(crate) useself::counters::THREADS_MAX; useself::counters::{AtomicCounters, JobsEventCounter};
/// The `Sleep` struct is embedded into each registry. It governs the waking and sleeping /// of workers. It has callbacks that are invoked periodically at significant events, /// such as when workers are looping and looking for work, when latches are set, or when /// jobs are published, and it either blocks threads or wakes them in response to these /// events. See the [`README.md`] in this module for more details. /// /// [`README.md`] README.md pub(super) struct Sleep { /// One "sleep state" per worker. Used to track if a worker is sleeping and to have /// them block.
worker_sleep_states: Vec<CachePadded<WorkerSleepState>>,
counters: AtomicCounters,
}
/// An instance of this struct is created when a thread becomes idle. /// It is consumed when the thread finds work, and passed by `&mut` /// reference for operations that preserve the idle state. (In other /// words, producing one of these structs is evidence the thread is /// idle.) It tracks state such as how long the thread has been idle. pub(super) struct IdleState { /// What is worker index of the idle thread?
worker_index: usize,
/// How many rounds have we been circling without sleeping?
rounds: u32,
/// Once we become sleepy, what was the sleepy counter value? /// Set to `INVALID_SLEEPY_COUNTER` otherwise.
jobs_counter: JobsEventCounter,
}
/// The "sleep state" for an individual worker. #[derive(Default)] struct WorkerSleepState { /// Set to true when the worker goes to sleep; set to false when /// the worker is notified or when it wakes.
is_blocked: Mutex<bool>,
#[inline] pub(super) fn work_found(&self) { // If we were the last idle thread and other threads are still sleeping, // then we should wake up another thread. let threads_to_wake = self.counters.sub_inactive_thread(); self.wake_any_threads(threads_to_wake as u32);
}
let sleep_state = &self.worker_sleep_states[worker_index]; letmut is_blocked = sleep_state.is_blocked.lock().unwrap();
debug_assert!(!*is_blocked);
// Our latch was signalled. We should wake back up fully as we // will have some stuff to do. if !latch.fall_asleep() {
idle_state.wake_fully(); return;
}
loop { let counters = self.counters.load(Ordering::SeqCst);
// Check if the JEC has changed since we got sleepy.
debug_assert!(idle_state.jobs_counter.is_sleepy()); if counters.jobs_counter() != idle_state.jobs_counter { // JEC has changed, so a new job was posted, but for some reason // we didn't see it. We should return to just before the SLEEPY // state so we can do another search and (if we fail to find // work) go back to sleep.
idle_state.wake_partly();
latch.wake_up(); return;
}
// Otherwise, let's move from IDLE to SLEEPING. ifself.counters.try_add_sleeping_thread(counters) { break;
}
}
// Successfully registered as asleep.
// We have one last check for injected jobs to do. This protects against // deadlock in the very unlikely event that // // - an external job is being injected while we are sleepy // - that job triggers the rollover over the JEC such that we don't see it // - we are the last active worker thread
std::sync::atomic::fence(Ordering::SeqCst); if has_injected_jobs() { // If we see an externally injected job, then we have to 'wake // ourselves up'. (Ordinarily, `sub_sleeping_thread` is invoked by // the one that wakes us.) self.counters.sub_sleeping_thread();
} else { // If we don't see an injected job (the normal case), then flag // ourselves as asleep and wait till we are notified. // // (Note that `is_blocked` is held under a mutex and the mutex was // acquired *before* we incremented the "sleepy counter". This means // that whomever is coming to wake us will have to wait until we // release the mutex in the call to `wait`, so they will see this // boolean as true.)
*is_blocked = true; while *is_blocked {
is_blocked = sleep_state.condvar.wait(is_blocked).unwrap();
}
}
// Update other state:
idle_state.wake_fully();
latch.wake_up();
}
/// Notify the given thread that it should wake up (if it is /// sleeping). When this method is invoked, we typically know the /// thread is asleep, though in rare cases it could have been /// awoken by (e.g.) new work having been posted. pub(super) fn notify_worker_latch_is_set(&self, target_worker_index: usize) { self.wake_specific_thread(target_worker_index);
}
/// Signals that `num_jobs` new jobs were injected into the thread /// pool from outside. This function will ensure that there are /// threads available to process them, waking threads from sleep /// if necessary. /// /// # Parameters /// /// - `num_jobs` -- lower bound on number of jobs available for stealing. /// We'll try to get at least one thread per job. #[inline] pub(super) fn new_injected_jobs(&self, num_jobs: u32, queue_was_empty: bool) { // This fence is needed to guarantee that threads // as they are about to fall asleep, observe any // new jobs that may have been injected.
std::sync::atomic::fence(Ordering::SeqCst);
self.new_jobs(num_jobs, queue_was_empty)
}
/// Signals that `num_jobs` new jobs were pushed onto a thread's /// local deque. This function will try to ensure that there are /// threads available to process them, waking threads from sleep /// if necessary. However, this is not guaranteed: under certain /// race conditions, the function may fail to wake any new /// threads; in that case the existing thread should eventually /// pop the job. /// /// # Parameters /// /// - `num_jobs` -- lower bound on number of jobs available for stealing. /// We'll try to get at least one thread per job. #[inline] pub(super) fn new_internal_jobs(&self, num_jobs: u32, queue_was_empty: bool) { self.new_jobs(num_jobs, queue_was_empty)
}
/// Common helper for `new_injected_jobs` and `new_internal_jobs`. #[inline] fn new_jobs(&self, num_jobs: u32, queue_was_empty: bool) { // Read the counters and -- if sleepy workers have announced themselves // -- announce that there is now work available. The final value of `counters` // with which we exit the loop thus corresponds to a state when let counters = self
.counters
.increment_jobs_event_counter_if(JobsEventCounter::is_sleepy); let num_awake_but_idle = counters.awake_but_idle_threads(); let num_sleepers = counters.sleeping_threads();
if num_sleepers == 0 { // nobody to wake return;
}
// Promote from u16 to u32 so we can interoperate with // num_jobs more easily. let num_awake_but_idle = num_awake_but_idle as u32; let num_sleepers = num_sleepers as u32;
// If the queue is non-empty, then we always wake up a worker // -- clearly the existing idle jobs aren't enough. Otherwise, // check to see if we have enough idle workers. if !queue_was_empty { let num_to_wake = std::cmp::min(num_jobs, num_sleepers); self.wake_any_threads(num_to_wake);
} elseif num_awake_but_idle < num_jobs { let num_to_wake = std::cmp::min(num_jobs - num_awake_but_idle, num_sleepers); self.wake_any_threads(num_to_wake);
}
}
#[cold] fn wake_any_threads(&self, mut num_to_wake: u32) { if num_to_wake > 0 { for i in0..self.worker_sleep_states.len() { ifself.wake_specific_thread(i) {
num_to_wake -= 1; if num_to_wake == 0 { return;
}
}
}
}
}
// When the thread went to sleep, it will have incremented // this value. When we wake it, its our job to decrement // it. We could have the thread do it, but that would // introduce a delay between when the thread was // *notified* and when this counter was decremented. That // might mislead people with new work into thinking that // there are sleeping threads that they should try to // wake, when in fact there is nothing left for them to // do. self.counters.sub_sleeping_thread();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.