use std::collections::VecDeque;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Condvar, Mutex};
use std::thread::JoinHandle;
type Task = Box<dyn FnOnce() + Send>;
struct Shared {
queue: Mutex<VecDeque<Task>>,
ready: Condvar,
shutdown: AtomicBool,
}
impl Shared {
fn push(&self, task: Task) {
self.queue.lock().unwrap().push_back(task);
self.ready.notify_one();
}
fn try_pop(&self) -> Option<Task> {
self.queue.lock().unwrap().pop_front()
}
}
pub(crate) struct ThreadPool {
shared: Arc<Shared>,
workers: usize,
handles: Vec<JoinHandle<()>>,
}
impl ThreadPool {
pub(crate) fn new(workers: usize) -> Self {
let shared = Arc::new(Shared {
queue: Mutex::new(VecDeque::new()),
ready: Condvar::new(),
shutdown: AtomicBool::new(false),
});
let mut handles = Vec::with_capacity(workers);
for _ in 0..workers {
let shared = shared.clone();
if let Ok(h) = std::thread::Builder::new()
.name("hpvca-pool".into())
.spawn(move || worker_loop(&shared))
{
handles.push(h);
}
}
let workers = handles.len();
ThreadPool {
shared,
workers,
handles,
}
}
pub(crate) fn parallelism(&self) -> usize {
self.workers + 1
}
pub(crate) fn scoped<'scope, F, R>(&'scope self, f: F) -> R
where
F: FnOnce(&Scope<'scope>) -> R,
{
let scope = Scope {
shared: &self.shared,
pending: AtomicUsize::new(0),
_marker: PhantomData,
};
let out = f(&scope);
scope.wait();
out
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
self.shared.shutdown.store(true, Ordering::Release);
self.shared.ready.notify_all();
for h in self.handles.drain(..) {
let _ = h.join();
}
}
}
fn worker_loop(shared: &Shared) {
loop {
let task = {
let mut q = shared.queue.lock().unwrap();
loop {
if let Some(t) = q.pop_front() {
break Some(t);
}
if shared.shutdown.load(Ordering::Acquire) {
break None;
}
q = shared.ready.wait(q).unwrap();
}
};
match task {
Some(t) => t(),
None => return,
}
}
}
pub(crate) struct Scope<'scope> {
shared: &'scope Shared,
pending: AtomicUsize,
_marker: PhantomData<&'scope ()>,
}
impl<'scope> Scope<'scope> {
pub(crate) fn spawn<F>(&self, task: F)
where
F: FnOnce() + Send + 'scope,
{
self.pending.fetch_add(1, Ordering::SeqCst);
let pending: &'scope AtomicUsize = unsafe { &*(&self.pending as *const AtomicUsize) };
let job: Box<dyn FnOnce() + Send + 'scope> = Box::new(move || {
task();
pending.fetch_sub(1, Ordering::SeqCst);
});
let job: Task =
unsafe { std::mem::transmute::<Box<dyn FnOnce() + Send + 'scope>, Task>(job) };
self.shared.push(job);
}
fn wait(&self) {
while self.pending.load(Ordering::SeqCst) != 0 {
match self.shared.try_pop() {
Some(task) => task(),
None => std::thread::yield_now(),
}
}
}
}