use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use crate::synca::LockSignal;
#[derive(Clone)]
pub struct WaitGroup {
count: Arc<AtomicUsize>,
signal: Arc<LockSignal>,
}
impl WaitGroup {
#[must_use]
pub fn new() -> Self {
Self {
count: Arc::new(AtomicUsize::new(0)),
signal: Arc::new(LockSignal::new()),
}
}
pub fn add(&self, n: usize) {
self.count.fetch_add(n, Ordering::SeqCst);
}
pub fn done(&self) {
let prev = self.count.fetch_sub(1, Ordering::SeqCst);
if prev == 1 {
self.signal.signal_all();
}
}
pub fn wait(&self) {
loop {
if self.count.load(Ordering::SeqCst) == 0 {
return;
}
self.signal.lock_and_wait();
}
}
#[must_use]
pub fn guard(&self) -> WaitGroupGuard {
WaitGroupGuard(self.clone())
}
}
impl Default for WaitGroup {
fn default() -> Self {
Self::new()
}
}
pub struct WaitGroupGuard(WaitGroup);
impl Drop for WaitGroupGuard {
fn drop(&mut self) {
self.0.done();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{sync::Arc, thread, time::Duration};
#[test]
fn test_waitgroup_basic() {
let wg = WaitGroup::new();
wg.add(3);
for _ in 0..3 {
let wg = wg.clone();
thread::spawn(move || {
let _guard = wg.guard();
thread::sleep(Duration::from_millis(10));
});
}
wg.wait(); }
#[test]
fn test_waitgroup_zero() {
let wg = WaitGroup::new();
wg.wait();
}
#[test]
fn test_waitgroup_add_done() {
let wg = WaitGroup::new();
wg.add(2);
wg.done();
wg.done();
wg.wait();
}
#[test]
fn test_waitgroup_guard_drop() {
let wg = WaitGroup::new();
wg.add(1);
{
let _guard = wg.guard();
}
wg.wait(); }
#[test]
fn test_waitgroup_concurrent() {
let wg = WaitGroup::new();
let results = Arc::new(std::sync::Mutex::new(Vec::new()));
for i in 0..10 {
let wg = wg.clone();
let results = Arc::clone(&results);
wg.add(1);
thread::spawn(move || {
let _guard = wg.guard();
thread::sleep(Duration::from_millis(i as u64));
results.lock().unwrap().push(i);
});
}
wg.wait();
let results = results.lock().unwrap();
assert_eq!(results.len(), 10);
}
}