use std::cell::UnsafeCell;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::thread::JoinHandle;
type Job = dyn Fn(usize) + Sync + 'static;
const SPIN: u32 = 1 << 14;
struct Shared {
epoch: AtomicU64,
job: UnsafeCell<*const Job>,
pending: AtomicUsize,
stop: AtomicBool,
}
unsafe impl Sync for Shared {}
unsafe impl Send for Shared {}
pub struct ForkJoin {
shared: Arc<Shared>,
workers: Vec<JoinHandle<()>>,
}
impl ForkJoin {
pub fn new(threads: usize) -> Self {
let shared = Arc::new(Shared {
epoch: AtomicU64::new(0),
job: UnsafeCell::new(std::ptr::null::<fn(usize)>() as *const Job),
pending: AtomicUsize::new(0),
stop: AtomicBool::new(false),
});
let workers = (1..threads.max(1))
.map(|share| {
let sh = Arc::clone(&shared);
std::thread::spawn(move || worker(&sh, share))
})
.collect();
ForkJoin { shared, workers }
}
pub fn threads(&self) -> usize {
self.workers.len() + 1
}
pub fn run(&self, job: &(dyn Fn(usize) + Sync)) {
let sh = &*self.shared;
unsafe {
*sh.job.get() = std::mem::transmute::<&(dyn Fn(usize) + Sync), *const Job>(job);
}
sh.pending.store(self.workers.len(), Ordering::Relaxed);
sh.epoch.fetch_add(1, Ordering::Release);
for w in &self.workers {
w.thread().unpark();
}
job(0);
while sh.pending.load(Ordering::Acquire) != 0 {
std::hint::spin_loop();
}
}
}
impl Drop for ForkJoin {
fn drop(&mut self) {
self.shared.stop.store(true, Ordering::Release);
self.shared.epoch.fetch_add(1, Ordering::Release);
for w in self.workers.drain(..) {
w.thread().unpark();
let _ = w.join();
}
}
}
fn worker(sh: &Shared, share: usize) {
let mut seen = 0u64;
loop {
let mut spins = 0u32;
loop {
let e = sh.epoch.load(Ordering::Acquire);
if e != seen {
seen = e;
break;
}
if spins < SPIN {
spins += 1;
std::hint::spin_loop();
} else {
std::thread::park();
}
}
if sh.stop.load(Ordering::Acquire) {
return;
}
let job = unsafe { &**sh.job.get() };
job(share);
sh.pending.fetch_sub(1, Ordering::Release);
}
}