215 lines
5.2 KiB
Rust
215 lines
5.2 KiB
Rust
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
|
// file at the top-level directory of this distribution and at
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
// option. This file may not be copied, modified, or distributed
|
|
// except according to those terms.
|
|
|
|
//! Abstraction of a thread pool for basic parallelism.
|
|
|
|
#![unstable(feature = "std_misc",
|
|
reason = "the semantics of a failing task and whether a thread is \
|
|
re-attached to a thread pool are somewhat unclear, and the \
|
|
utility of this type in `std::sync` is questionable with \
|
|
respect to the jobs of other primitives")]
|
|
|
|
use core::prelude::*;
|
|
|
|
use sync::{Arc, Mutex};
|
|
use sync::mpsc::{channel, Sender, Receiver};
|
|
use thread::Thread;
|
|
use thunk::Thunk;
|
|
|
|
struct Sentinel<'a> {
|
|
jobs: &'a Arc<Mutex<Receiver<Thunk>>>,
|
|
active: bool
|
|
}
|
|
|
|
impl<'a> Sentinel<'a> {
|
|
fn new(jobs: &Arc<Mutex<Receiver<Thunk>>>) -> Sentinel {
|
|
Sentinel {
|
|
jobs: jobs,
|
|
active: true
|
|
}
|
|
}
|
|
|
|
// Cancel and destroy this sentinel.
|
|
fn cancel(mut self) {
|
|
self.active = false;
|
|
}
|
|
}
|
|
|
|
#[unsafe_destructor]
|
|
impl<'a> Drop for Sentinel<'a> {
|
|
fn drop(&mut self) {
|
|
if self.active {
|
|
spawn_in_pool(self.jobs.clone())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A thread pool used to execute functions in parallel.
|
|
///
|
|
/// Spawns `n` worker threads and replenishes the pool if any worker threads
|
|
/// panic.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust
|
|
/// use std::sync::TaskPool;
|
|
/// use std::iter::AdditiveIterator;
|
|
/// use std::sync::mpsc::channel;
|
|
///
|
|
/// let pool = TaskPool::new(4u);
|
|
///
|
|
/// let (tx, rx) = channel();
|
|
/// for _ in 0..8u {
|
|
/// let tx = tx.clone();
|
|
/// pool.execute(move|| {
|
|
/// tx.send(1u).unwrap();
|
|
/// });
|
|
/// }
|
|
///
|
|
/// assert_eq!(rx.iter().take(8u).sum(), 8u);
|
|
/// ```
|
|
pub struct TaskPool {
|
|
// How the threadpool communicates with subthreads.
|
|
//
|
|
// This is the only such Sender, so when it is dropped all subthreads will
|
|
// quit.
|
|
jobs: Sender<Thunk>
|
|
}
|
|
|
|
impl TaskPool {
|
|
/// Spawns a new thread pool with `threads` threads.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This function will panic if `threads` is 0.
|
|
pub fn new(threads: uint) -> TaskPool {
|
|
assert!(threads >= 1);
|
|
|
|
let (tx, rx) = channel::<Thunk>();
|
|
let rx = Arc::new(Mutex::new(rx));
|
|
|
|
// Threadpool threads
|
|
for _ in 0..threads {
|
|
spawn_in_pool(rx.clone());
|
|
}
|
|
|
|
TaskPool { jobs: tx }
|
|
}
|
|
|
|
/// Executes the function `job` on a thread in the pool.
|
|
pub fn execute<F>(&self, job: F)
|
|
where F : FnOnce(), F : Send
|
|
{
|
|
self.jobs.send(Thunk::new(job)).unwrap();
|
|
}
|
|
}
|
|
|
|
fn spawn_in_pool(jobs: Arc<Mutex<Receiver<Thunk>>>) {
|
|
Thread::spawn(move || {
|
|
// Will spawn a new thread on panic unless it is cancelled.
|
|
let sentinel = Sentinel::new(&jobs);
|
|
|
|
loop {
|
|
let message = {
|
|
// Only lock jobs for the time it takes
|
|
// to get a job, not run it.
|
|
let lock = jobs.lock().unwrap();
|
|
lock.recv()
|
|
};
|
|
|
|
match message {
|
|
Ok(job) => job.invoke(()),
|
|
|
|
// The Taskpool was dropped.
|
|
Err(..) => break
|
|
}
|
|
}
|
|
|
|
sentinel.cancel();
|
|
});
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use prelude::v1::*;
|
|
use super::*;
|
|
use sync::mpsc::channel;
|
|
|
|
const TEST_TASKS: uint = 4u;
|
|
|
|
#[test]
|
|
fn test_works() {
|
|
use iter::AdditiveIterator;
|
|
|
|
let pool = TaskPool::new(TEST_TASKS);
|
|
|
|
let (tx, rx) = channel();
|
|
for _ in 0..TEST_TASKS {
|
|
let tx = tx.clone();
|
|
pool.execute(move|| {
|
|
tx.send(1u).unwrap();
|
|
});
|
|
}
|
|
|
|
assert_eq!(rx.iter().take(TEST_TASKS).sum(), TEST_TASKS);
|
|
}
|
|
|
|
#[test]
|
|
#[should_fail]
|
|
fn test_zero_tasks_panic() {
|
|
TaskPool::new(0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_recovery_from_subtask_panic() {
|
|
use iter::AdditiveIterator;
|
|
|
|
let pool = TaskPool::new(TEST_TASKS);
|
|
|
|
// Panic all the existing threads.
|
|
for _ in 0..TEST_TASKS {
|
|
pool.execute(move|| -> () { panic!() });
|
|
}
|
|
|
|
// Ensure new threads were spawned to compensate.
|
|
let (tx, rx) = channel();
|
|
for _ in 0..TEST_TASKS {
|
|
let tx = tx.clone();
|
|
pool.execute(move|| {
|
|
tx.send(1u).unwrap();
|
|
});
|
|
}
|
|
|
|
assert_eq!(rx.iter().take(TEST_TASKS).sum(), TEST_TASKS);
|
|
}
|
|
|
|
#[test]
|
|
fn test_should_not_panic_on_drop_if_subtasks_panic_after_drop() {
|
|
use sync::{Arc, Barrier};
|
|
|
|
let pool = TaskPool::new(TEST_TASKS);
|
|
let waiter = Arc::new(Barrier::new(TEST_TASKS + 1));
|
|
|
|
// Panic all the existing threads in a bit.
|
|
for _ in 0..TEST_TASKS {
|
|
let waiter = waiter.clone();
|
|
pool.execute(move|| {
|
|
waiter.wait();
|
|
panic!();
|
|
});
|
|
}
|
|
|
|
drop(pool);
|
|
|
|
// Kick off the failure.
|
|
waiter.wait();
|
|
}
|
|
}
|