mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-24 00:00:11 +02:00
threadpool: introduce a global task queue (#798)
This commit is contained in:
committed by
Toby Lawrence
parent
201b6ce53a
commit
fdf4aba621
@@ -1,12 +1,12 @@
|
||||
use park::{BoxPark, BoxUnpark};
|
||||
use task::{Task, Queue};
|
||||
use task::Task;
|
||||
use worker::state::{State, PUSHED_MASK};
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::atomic::Ordering::{Acquire, AcqRel, Relaxed};
|
||||
use std::sync::atomic::Ordering::{AcqRel, Relaxed};
|
||||
|
||||
use crossbeam_utils::CachePadded;
|
||||
use deque;
|
||||
@@ -36,9 +36,6 @@ pub(crate) struct WorkerEntry {
|
||||
|
||||
// Thread unparker
|
||||
pub unpark: BoxUnpark,
|
||||
|
||||
// MPSC queue of jobs submitted to the worker from an external source.
|
||||
pub inbound: Queue,
|
||||
}
|
||||
|
||||
impl WorkerEntry {
|
||||
@@ -50,21 +47,11 @@ impl WorkerEntry {
|
||||
next_sleeper: UnsafeCell::new(0),
|
||||
worker: w,
|
||||
stealer: s,
|
||||
inbound: Queue::new(),
|
||||
park: UnsafeCell::new(park),
|
||||
unpark,
|
||||
}
|
||||
}
|
||||
|
||||
/// Atomically load the worker's state
|
||||
///
|
||||
/// # Ordering
|
||||
///
|
||||
/// An `Acquire` ordering is established on the entry's state variable.
|
||||
pub fn load_state(&self) -> State {
|
||||
self.state.load(Acquire).into()
|
||||
}
|
||||
|
||||
/// Atomically unset the pushed flag.
|
||||
///
|
||||
/// # Return
|
||||
@@ -85,20 +72,15 @@ impl WorkerEntry {
|
||||
self.push_internal(task);
|
||||
}
|
||||
|
||||
/// Submits a task to the worker. This assumes that the caller is external
|
||||
/// to the worker. Internal submissions go through another path.
|
||||
///
|
||||
/// Returns `false` if the worker needs to be spawned.
|
||||
/// Notifies the worker and returns `false` if it needs to be spawned.
|
||||
///
|
||||
/// # Ordering
|
||||
///
|
||||
/// The `state` must have been obtained with an `Acquire` ordering.
|
||||
pub fn submit_external(&self, task: Arc<Task>, mut state: State) -> bool {
|
||||
#[inline]
|
||||
pub fn notify(&self, mut state: State) -> bool {
|
||||
use worker::Lifecycle::*;
|
||||
|
||||
// Push the task onto the external queue
|
||||
self.push_external(task);
|
||||
|
||||
loop {
|
||||
let mut next = state;
|
||||
next.notify();
|
||||
@@ -188,6 +170,7 @@ impl WorkerEntry {
|
||||
///
|
||||
/// This **must** only be called by the thread that owns the worker entry.
|
||||
/// This function is not `Sync`.
|
||||
#[inline]
|
||||
pub fn pop_task(&self) -> deque::Pop<Arc<Task>> {
|
||||
self.worker.pop()
|
||||
}
|
||||
@@ -208,22 +191,17 @@ impl WorkerEntry {
|
||||
///
|
||||
/// This is called when the pool is shutting down.
|
||||
pub fn drain_tasks(&self) {
|
||||
use deque::Pop;
|
||||
use deque::Pop::*;
|
||||
|
||||
loop {
|
||||
match self.worker.pop() {
|
||||
Pop::Data(_) => {}
|
||||
Pop::Empty => break,
|
||||
Pop::Retry => {}
|
||||
Data(_) => {}
|
||||
Empty => break,
|
||||
Retry => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn push_external(&self, task: Arc<Task>) {
|
||||
self.inbound.push(task);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push_internal(&self, task: Arc<Task>) {
|
||||
self.worker.push(task);
|
||||
@@ -254,7 +232,6 @@ impl fmt::Debug for WorkerEntry {
|
||||
.field("stealer", &self.stealer)
|
||||
.field("park", &"UnsafeCell<BoxPark>")
|
||||
.field("unpark", &"BoxUnpark")
|
||||
.field("inbound", &self.inbound)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,10 +242,6 @@ impl Worker {
|
||||
while self.check_run_state(first) {
|
||||
first = false;
|
||||
|
||||
// Poll inbound until empty, transferring all tasks to the internal
|
||||
// queue.
|
||||
let consistent = self.drain_inbound();
|
||||
|
||||
// Run the next available task
|
||||
if self.try_run_task(¬ify) {
|
||||
if self.is_blocking.get() {
|
||||
@@ -253,6 +249,8 @@ impl Worker {
|
||||
return;
|
||||
}
|
||||
|
||||
// Poll the reactor and the global queue every now and then to
|
||||
// ensure no task gets left behind.
|
||||
if tick % LIGHT_SLEEP_INTERVAL == 0 {
|
||||
self.sleep_light();
|
||||
}
|
||||
@@ -264,11 +262,6 @@ impl Worker {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !consistent {
|
||||
spin_cnt = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
spin_cnt += 1;
|
||||
|
||||
// Yield the thread several times before it actually goes to sleep.
|
||||
@@ -423,7 +416,7 @@ impl Worker {
|
||||
if idx < len {
|
||||
match self.pool.workers[idx].steal_tasks(self.entry()) {
|
||||
Steal::Data(task) => {
|
||||
trace!("stole task");
|
||||
trace!("stole task from another worker");
|
||||
|
||||
self.run_task(task, notify);
|
||||
|
||||
@@ -562,48 +555,6 @@ impl Worker {
|
||||
task.run(notify)
|
||||
}
|
||||
|
||||
/// Drains all tasks on the extern queue and pushes them onto the internal
|
||||
/// queue.
|
||||
///
|
||||
/// Returns `true` if the operation was able to complete in a consistent
|
||||
/// state.
|
||||
#[inline]
|
||||
fn drain_inbound(&self) -> bool {
|
||||
use task::Poll::*;
|
||||
|
||||
let mut found_work = false;
|
||||
|
||||
loop {
|
||||
let task = unsafe { self.entry().inbound.poll() };
|
||||
|
||||
match task {
|
||||
Empty => {
|
||||
if found_work {
|
||||
// TODO: Why is this called on every iteration? Would it
|
||||
// not be better to only signal when work was found
|
||||
// after waking up?
|
||||
trace!("found work while draining; signal_work");
|
||||
self.pool.signal_work(&self.pool);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
Inconsistent => {
|
||||
if found_work {
|
||||
trace!("found work while draining; signal_work");
|
||||
self.pool.signal_work(&self.pool);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
Data(task) => {
|
||||
found_work = true;
|
||||
self.entry().push_internal(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Put the worker to sleep
|
||||
///
|
||||
/// Returns `true` if woken up due to new work arriving.
|
||||
@@ -679,18 +630,16 @@ impl Worker {
|
||||
|
||||
trace!(" -> starting to sleep; idx={}", self.id.0);
|
||||
|
||||
// Do a quick check to see if there are any notifications in the
|
||||
// reactor or new tasks in the global queue. Since this call will
|
||||
// clear the wakeup token, we need to check the state again and
|
||||
// only after that go to sleep.
|
||||
self.sleep_light();
|
||||
|
||||
// The state has been transitioned to sleeping, we can now wait by
|
||||
// calling the parker. This is done in a loop as condvars can wakeup
|
||||
// spuriously.
|
||||
loop {
|
||||
unsafe {
|
||||
(*self.entry().park.get())
|
||||
.park()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
trace!(" -> wakeup; idx={}", self.id.0);
|
||||
|
||||
// Reload the state
|
||||
state = self.entry().state.load(Acquire).into();
|
||||
|
||||
@@ -722,18 +671,38 @@ impl Worker {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(*self.entry().park.get())
|
||||
.park()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
trace!(" -> wakeup; idx={}", self.id.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// This doesn't actually put the thread to sleep. It calls
|
||||
/// `park.park_timeout` with a duration of 0. This allows the park
|
||||
/// implementation to perform any work that might be done on an interval.
|
||||
///
|
||||
/// Returns `true` if this worker has tasks in its queue.
|
||||
fn sleep_light(&self) {
|
||||
const STEAL_COUNT: usize = 32;
|
||||
|
||||
unsafe {
|
||||
(*self.entry().park.get())
|
||||
.park_timeout(Duration::from_millis(0))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..STEAL_COUNT {
|
||||
if let Some(task) = self.pool.queue.pop() {
|
||||
self.pool.submit(task, &self.pool);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn entry(&self) -> &Entry {
|
||||
@@ -747,14 +716,8 @@ impl Drop for Worker {
|
||||
trace!("shutting down thread; idx={}", self.id.0);
|
||||
|
||||
if self.should_finalize.get() {
|
||||
// Get all inbound work and push it onto the work queue. The work
|
||||
// queue is drained in the next step.
|
||||
self.drain_inbound();
|
||||
|
||||
// Drain the work queue
|
||||
self.entry().drain_tasks();
|
||||
|
||||
// TODO: Drain the work queue...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user