scoped_threadpool-0.1.9/.gitignore010064400017500001750000000000221264745665600154470ustar0000000000000000target Cargo.lock scoped_threadpool-0.1.9/.travis.yml010064400017500001750000000022621264745665700156010ustar0000000000000000language: rust rust: - nightly - beta - stable before_script: - | pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH script: - | travis-cargo build && travis-cargo test && rustdoc --test README.md -L dependency=./target/debug/deps --extern scoped_threadpool=./target/debug/libscoped_threadpool.rlib && travis-cargo bench && travis-cargo --only stable doc after_success: - travis-cargo --only stable doc-upload env: global: - TRAVIS_CARGO_NIGHTLY_FEATURE=nightly - secure: WdOoICzaAJej1J9AbDWGu+xN58NWbzICGkgE1Lxe7gW8CdJbFeWEDL/M+kSKOVrRjoHRdHrpVP7zkTQzpwKzKhnXMGn2uzlRaC0MYvKPfO8OK3hZiiUDaP6kdwFTFrPbhTxZfLQBysuMWAWEy1CbJ+kjVFexoVMgG/KAPLc+26n6UkV7dMirW3anNY3gkgkHijnwdGinazzZB1fQjy8dHxGVnnAYhMAAoinpcrrhLPYy3k/6nB12Njc3dzXGYe33jepznjnsQejTS/Mk1HLL1Iov+Lj/lM6TQJA+wl7x62FE+uPBYEK2aTTybN79rajFXLAZH+ZBO2Kep7iVKqa1R/5bk93X6X6aBc7jcUA7B8dbOS1o2s1cU+T+xL0mk99UXeLPZDUkEHiHppoJGy3wdyUkZvQCdybVfGDvhf9FlmJ6AvSH8E4MICvlxoNitPE6LlGdO2si2Jc5O99/GvHczjfx6oYWqPIhGo8Zkg91t1JWbwGD3Umzzl2BYT9D6v5o2zMHWzPmso3NlvVeP2YvbteWqPCrtZO7AobaENJSiRTfbv/rj4aVzsRQUt6DFMqzZnNPvwDMXNf8JY+pAlxjDj3dHreDKjgKC3C2SO73ObwpqoFlsFB/iz18lIz3ylscr9PRcXXXbJL2uV7zZAant3bbqB62eS2Zfk4IsEzByNk= scoped_threadpool-0.1.9/Cargo.toml.orig010064400017500001750000000007561324311432000163300ustar0000000000000000[package] name = "scoped_threadpool" version = "0.1.9" authors = ["Marvin Löbel "] license = "MIT" description = "A library for scoped and cached threadpools." readme = "README.md" documentation = "http://kimundi.github.io/scoped-threadpool-rs/scoped_threadpool/index.html" repository = "https://github.com/Kimundi/scoped-threadpool-rs" keywords = ["thread", "scoped", "pool", "cached", "threadpool"] [dev-dependencies] lazy_static = "1.0" [features] nightly = [] scoped_threadpool-0.1.9/Cargo.toml0000644000000017770000000000000126120ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "scoped_threadpool" version = "0.1.9" authors = ["Marvin Löbel "] description = "A library for scoped and cached threadpools." documentation = "http://kimundi.github.io/scoped-threadpool-rs/scoped_threadpool/index.html" readme = "README.md" keywords = ["thread", "scoped", "pool", "cached", "threadpool"] license = "MIT" repository = "https://github.com/Kimundi/scoped-threadpool-rs" [dev-dependencies.lazy_static] version = "1.0" [features] nightly = [] scoped_threadpool-0.1.9/LICENSE010064400017500001750000000020701264745665700144720ustar0000000000000000The MIT License (MIT) Copyright (c) 2015 Marvin Löbel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. scoped_threadpool-0.1.9/README.md010064400017500001750000000026341301313426400147210ustar0000000000000000scoped-threadpool-rs ============== [![Travis-CI Status](https://travis-ci.org/Kimundi/scoped-threadpool-rs.png?branch=master)](https://travis-ci.org/Kimundi/scoped-threadpool-rs) A library for scoped and cached threadpools. For more details, see the [docs](http://kimundi.github.io/scoped-threadpool-rs/scoped_threadpool/index.html). # Getting Started [scoped-threadpool-rs is available on crates.io](https://crates.io/crates/scoped_threadpool). Add the following dependency to your Cargo manifest to get the latest version of the 0.1 branch: ```toml [dependencies] scoped_threadpool = "0.1.*" ``` To always get the latest version, add this git repository to your Cargo manifest: ```toml [dependencies.scoped_threadpool] git = "https://github.com/Kimundi/scoped-threadpool-rs" ``` # Example ```rust extern crate scoped_threadpool; use scoped_threadpool::Pool; fn main() { // Create a threadpool holding 4 threads let mut pool = Pool::new(4); let mut vec = vec![0, 1, 2, 3, 4, 5, 6, 7]; // Use the threads as scoped threads that can // reference anything outside this closure pool.scoped(|scoped| { // Create references to each element in the vector ... for e in &mut vec { // ... and add 1 to it in a seperate thread scoped.execute(move || { *e += 1; }); } }); assert_eq!(vec, vec![1, 2, 3, 4, 5, 6, 7, 8]); } ``` scoped_threadpool-0.1.9/src/lib.rs010064400017500001750000000357331316325102100153500ustar0000000000000000//! This crate provides a stable, safe and scoped threadpool. //! //! It can be used to execute a number of short-lived jobs in parallel //! without the need to respawn the underlying threads. //! //! Jobs are runnable by borrowing the pool for a given scope, during which //! an arbitrary number of them can be executed. These jobs can access data of //! any lifetime outside of the pools scope, which allows working on //! non-`'static` references in parallel. //! //! For safety reasons, a panic inside a worker thread will not be isolated, //! but rather propagate to the outside of the pool. //! //! # Examples: //! //! ```rust //! extern crate scoped_threadpool; //! use scoped_threadpool::Pool; //! //! fn main() { //! // Create a threadpool holding 4 threads //! let mut pool = Pool::new(4); //! //! let mut vec = vec![0, 1, 2, 3, 4, 5, 6, 7]; //! //! // Use the threads as scoped threads that can //! // reference anything outside this closure //! pool.scoped(|scope| { //! // Create references to each element in the vector ... //! for e in &mut vec { //! // ... and add 1 to it in a seperate thread //! scope.execute(move || { //! *e += 1; //! }); //! } //! }); //! //! assert_eq!(vec, vec![1, 2, 3, 4, 5, 6, 7, 8]); //! } //! ``` #![cfg_attr(all(feature="nightly", test), feature(test))] #![cfg_attr(feature="nightly", feature(drop_types_in_const))] #![cfg_attr(all(feature="nightly", test), feature(core_intrinsics))] #![cfg_attr(feature="nightly", feature(const_fn))] #![cfg_attr(feature="nightly", feature(const_unsafe_cell_new))] #![warn(missing_docs)] #[macro_use] #[cfg(test)] extern crate lazy_static; use std::thread::{self, JoinHandle}; use std::sync::mpsc::{channel, Sender, Receiver, SyncSender, sync_channel, RecvError}; use std::sync::{Arc, Mutex}; use std::marker::PhantomData; use std::mem; enum Message { NewJob(Thunk<'static>), Join, } trait FnBox { fn call_box(self: Box); } impl FnBox for F { fn call_box(self: Box) { (*self)() } } type Thunk<'a> = Box; impl Drop for Pool { fn drop(&mut self) { self.job_sender = None; } } /// A threadpool that acts as a handle to a number /// of threads spawned at construction. pub struct Pool { threads: Vec, job_sender: Option> } struct ThreadData { _thread_join_handle: JoinHandle<()>, pool_sync_rx: Receiver<()>, thread_sync_tx: SyncSender<()>, } impl Pool { /// Construct a threadpool with the given number of threads. /// Minimum value is `1`. pub fn new(n: u32) -> Pool { assert!(n >= 1); let (job_sender, job_receiver) = channel(); let job_receiver = Arc::new(Mutex::new(job_receiver)); let mut threads = Vec::with_capacity(n as usize); // spawn n threads, put them in waiting mode for _ in 0..n { let job_receiver = job_receiver.clone(); let (pool_sync_tx, pool_sync_rx) = sync_channel::<()>(0); let (thread_sync_tx, thread_sync_rx) = sync_channel::<()>(0); let thread = thread::spawn(move || { loop { let message = { // Only lock jobs for the time it takes // to get a job, not run it. let lock = job_receiver.lock().unwrap(); lock.recv() }; match message { Ok(Message::NewJob(job)) => { job.call_box(); } Ok(Message::Join) => { // Syncronize/Join with pool. // This has to be a two step // process to ensure that all threads // finished their work before the pool // can continue // Wait until the pool started syncing with threads if pool_sync_tx.send(()).is_err() { // The pool was dropped. break; } // Wait until the pool finished syncing with threads if thread_sync_rx.recv().is_err() { // The pool was dropped. break; } } Err(..) => { // The pool was dropped. break } } } }); threads.push(ThreadData { _thread_join_handle: thread, pool_sync_rx: pool_sync_rx, thread_sync_tx: thread_sync_tx, }); } Pool { threads: threads, job_sender: Some(job_sender), } } /// Borrows the pool and allows executing jobs on other /// threads during that scope via the argument of the closure. /// /// This method will block until the closure and all its jobs have /// run to completion. pub fn scoped<'pool, 'scope, F, R>(&'pool mut self, f: F) -> R where F: FnOnce(&Scope<'pool, 'scope>) -> R { let scope = Scope { pool: self, _marker: PhantomData, }; f(&scope) } /// Returns the number of threads inside this pool. pub fn thread_count(&self) -> u32 { self.threads.len() as u32 } } ///////////////////////////////////////////////////////////////////////////// /// Handle to the scope during which the threadpool is borrowed. pub struct Scope<'pool, 'scope> { pool: &'pool mut Pool, // The 'scope needs to be invariant... it seems? _marker: PhantomData<::std::cell::Cell<&'scope mut ()>>, } impl<'pool, 'scope> Scope<'pool, 'scope> { /// Execute a job on the threadpool. /// /// The body of the closure will be send to one of the /// internal threads, and this method itself will not wait /// for its completion. pub fn execute(&self, f: F) where F: FnOnce() + Send + 'scope { self.execute_(f) } fn execute_(&self, f: F) where F: FnOnce() + Send + 'scope { let b = unsafe { mem::transmute::, Thunk<'static>>(Box::new(f)) }; self.pool.job_sender.as_ref().unwrap().send(Message::NewJob(b)).unwrap(); } /// Blocks until all currently queued jobs have run to completion. pub fn join_all(&self) { for _ in 0..self.pool.threads.len() { self.pool.job_sender.as_ref().unwrap().send(Message::Join).unwrap(); } // Synchronize/Join with threads // This has to be a two step process // to make sure _all_ threads received _one_ Join message each. // This loop will block on every thread until it // received and reacted to its Join message. let mut worker_panic = false; for thread_data in &self.pool.threads { if let Err(RecvError) = thread_data.pool_sync_rx.recv() { worker_panic = true; } } if worker_panic { // Now that all the threads are paused, we can safely panic panic!("Thread pool worker panicked"); } // Once all threads joined the jobs, send them a continue message for thread_data in &self.pool.threads { thread_data.thread_sync_tx.send(()).unwrap(); } } } impl<'pool, 'scope> Drop for Scope<'pool, 'scope> { fn drop(&mut self) { self.join_all(); } } ///////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { #![cfg_attr(feature="nightly", allow(unused_unsafe))] use super::Pool; use std::thread; use std::sync; use std::time; fn sleep_ms(ms: u64) { thread::sleep(time::Duration::from_millis(ms)); } #[test] fn smoketest() { let mut pool = Pool::new(4); for i in 1..7 { let mut vec = vec![0, 1, 2, 3, 4]; pool.scoped(|s| { for e in vec.iter_mut() { s.execute(move || { *e += i; }); } }); let mut vec2 = vec![0, 1, 2, 3, 4]; for e in vec2.iter_mut() { *e += i; } assert_eq!(vec, vec2); } } #[test] #[should_panic] fn thread_panic() { let mut pool = Pool::new(4); pool.scoped(|scoped| { scoped.execute(move || { panic!() }); }); } #[test] #[should_panic] fn scope_panic() { let mut pool = Pool::new(4); pool.scoped(|_scoped| { panic!() }); } #[test] #[should_panic] fn pool_panic() { let _pool = Pool::new(4); panic!() } #[test] fn join_all() { let mut pool = Pool::new(4); let (tx_, rx) = sync::mpsc::channel(); pool.scoped(|scoped| { let tx = tx_.clone(); scoped.execute(move || { sleep_ms(1000); tx.send(2).unwrap(); }); let tx = tx_.clone(); scoped.execute(move || { tx.send(1).unwrap(); }); scoped.join_all(); let tx = tx_.clone(); scoped.execute(move || { tx.send(3).unwrap(); }); }); assert_eq!(rx.iter().take(3).collect::>(), vec![1, 2, 3]); } #[test] fn join_all_with_thread_panic() { use std::sync::mpsc::Sender; struct OnScopeEnd(Sender); impl Drop for OnScopeEnd { fn drop(&mut self) { self.0.send(1).unwrap(); sleep_ms(200); } } let (tx_, rx) = sync::mpsc::channel(); // Use a thread here to handle the expected panic from the pool. Should // be switched to use panic::recover instead when it becomes stable. let handle = thread::spawn(move || { let mut pool = Pool::new(8); let _on_scope_end = OnScopeEnd(tx_.clone()); pool.scoped(|scoped| { scoped.execute(move || { sleep_ms(100); panic!(); }); for _ in 1..8 { let tx = tx_.clone(); scoped.execute(move || { sleep_ms(200); tx.send(0).unwrap(); }); } }); }); if let Ok(..) = handle.join() { panic!("Pool didn't panic as expected"); } // If the `1` that OnScopeEnd sent occurs anywhere else than at the // end, that means that a worker thread was still running even // after the `scoped` call finished, which is unsound. let values: Vec = rx.into_iter().collect(); assert_eq!(&values[..], &[0, 0, 0, 0, 0, 0, 0, 1]); } #[test] fn safe_execute() { let mut pool = Pool::new(4); pool.scoped(|scoped| { scoped.execute(move || { }); }); } } #[cfg(all(test, feature="nightly"))] mod benches { extern crate test; use self::test::{Bencher, black_box}; use super::Pool; use std::sync::Mutex; // const MS_SLEEP_PER_OP: u32 = 1; lazy_static! { static ref POOL_1: Mutex = Mutex::new(Pool::new(1)); static ref POOL_2: Mutex = Mutex::new(Pool::new(2)); static ref POOL_3: Mutex = Mutex::new(Pool::new(3)); static ref POOL_4: Mutex = Mutex::new(Pool::new(4)); static ref POOL_5: Mutex = Mutex::new(Pool::new(5)); static ref POOL_8: Mutex = Mutex::new(Pool::new(8)); } fn fib(n: u64) -> u64 { let mut prev_prev: u64 = 1; let mut prev = 1; let mut current = 1; for _ in 2..(n+1) { current = prev_prev.wrapping_add(prev); prev_prev = prev; prev = current; } current } fn threads_interleaved_n(pool: &mut Pool) { let size = 1024; // 1kiB let mut data = vec![1u8; size]; pool.scoped(|s| { for e in data.iter_mut() { s.execute(move || { *e += fib(black_box(1000 * (*e as u64))) as u8; for i in 0..10000 { black_box(i); } //sleep_ms(MS_SLEEP_PER_OP); }); } }); } #[bench] fn threads_interleaved_1(b: &mut Bencher) { b.iter(|| threads_interleaved_n(&mut POOL_1.lock().unwrap())) } #[bench] fn threads_interleaved_2(b: &mut Bencher) { b.iter(|| threads_interleaved_n(&mut POOL_2.lock().unwrap())) } #[bench] fn threads_interleaved_4(b: &mut Bencher) { b.iter(|| threads_interleaved_n(&mut POOL_4.lock().unwrap())) } #[bench] fn threads_interleaved_8(b: &mut Bencher) { b.iter(|| threads_interleaved_n(&mut POOL_8.lock().unwrap())) } fn threads_chunked_n(pool: &mut Pool) { // Set this to 1GB and 40 to get good but slooow results let size = 1024 * 1024 * 10 / 4; // 10MiB let bb_repeat = 50; let n = pool.thread_count(); let mut data = vec![0u32; size]; pool.scoped(|s| { let l = (data.len() - 1) / n as usize + 1; for es in data.chunks_mut(l) { s.execute(move || { if es.len() > 1 { es[0] = 1; es[1] = 1; for i in 2..es.len() { // Fibonnaci gets big fast, // so just wrap around all the time es[i] = black_box(es[i-1].wrapping_add(es[i-2])); for i in 0..bb_repeat { black_box(i); } } } //sleep_ms(MS_SLEEP_PER_OP); }); } }); } #[bench] fn threads_chunked_1(b: &mut Bencher) { b.iter(|| threads_chunked_n(&mut POOL_1.lock().unwrap())) } #[bench] fn threads_chunked_2(b: &mut Bencher) { b.iter(|| threads_chunked_n(&mut POOL_2.lock().unwrap())) } #[bench] fn threads_chunked_3(b: &mut Bencher) { b.iter(|| threads_chunked_n(&mut POOL_3.lock().unwrap())) } #[bench] fn threads_chunked_4(b: &mut Bencher) { b.iter(|| threads_chunked_n(&mut POOL_4.lock().unwrap())) } #[bench] fn threads_chunked_5(b: &mut Bencher) { b.iter(|| threads_chunked_n(&mut POOL_5.lock().unwrap())) } #[bench] fn threads_chunked_8(b: &mut Bencher) { b.iter(|| threads_chunked_n(&mut POOL_8.lock().unwrap())) } } scoped_threadpool-0.1.9/tests/threads-living-too-long-demo.rs010064400017500001750000000065461301314002400225450ustar0000000000000000extern crate scoped_threadpool; use scoped_threadpool::Pool; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT}; use std::panic::AssertUnwindSafe; // The representation invariant for PositivelyAtomic is that it is // always a positive integer. When we drop it, we store zero in // its value; but one should never observe that. pub struct PositivelyAtomic(AtomicUsize); impl Drop for PositivelyAtomic { fn drop(&mut self) { // Since we are being dropped, we will now break the // representation invariant. (And then clients will // subsequently observe that breakage.) self.0.store(0, Ordering::Relaxed); } } impl PositivelyAtomic { pub fn new(x: usize) -> PositivelyAtomic { assert!(x > 0); PositivelyAtomic(AtomicUsize::new(x)) } // Assuming the representation invariant holds, this should // always return a positive value. pub fn load(&self) -> usize { self.0.load(Ordering::Relaxed) } pub fn cas(&self, old: usize, new: usize) -> usize { assert!(new > 0); self.0.compare_and_swap(old, new, Ordering::Relaxed) } } #[test] fn demo_stack_allocated() { static SAW_ZERO: AtomicBool = ATOMIC_BOOL_INIT; for _i in 0..100 { let saw_zero = &AssertUnwindSafe(&SAW_ZERO); let _p = ::std::panic::catch_unwind(move || { let p = PositivelyAtomic::new(1); kernel(&p, saw_zero); }); if saw_zero.load(Ordering::Relaxed) { panic!("demo_stack_allocated saw zero!"); } } } #[test] fn demo_heap_allocated() { static SAW_ZERO: AtomicBool = ATOMIC_BOOL_INIT; for i in 0..100 { let saw_zero = &AssertUnwindSafe(&SAW_ZERO); let _p = ::std::panic::catch_unwind(move || { let mut v = Vec::with_capacity((i % 5)*1024 + i); v.push(PositivelyAtomic::new(1)); kernel(&v[0], saw_zero); }); if saw_zero.load(Ordering::Relaxed) { panic!("demo_heap_allocated saw zero!"); } } } pub fn kernel(r: &PositivelyAtomic, saw_zero: &AtomicBool) { // Create a threadpool holding 4 threads let mut pool = Pool::new(4); // Use the threads as scoped threads that can // reference anything outside this closure pool.scoped(|scope| { // Create references to each element in the vector ... for _ in 0..4 { scope.execute(move || { for _ in 0..100000 { let v = r.load(); if v == 0 { saw_zero.store(true, Ordering::Relaxed); panic!("SAW ZERO"); } let v_new = (v % 100) + 1; if v != r.cas(v, v_new) { // this is not a true panic condition // in the original scenario. // // it rather is a rare event, and I want to // emulate a rare panic occurring from one // thread (and then see how the overall // computation proceeds from there). panic!("interference"); } else { // incremented successfully } } }); } }); }