pax_global_header 0000666 0000000 0000000 00000000064 14370056340 0014514 g ustar 00root root 0000000 0000000 52 comment=052113009e7a515a40a8d7c13cfae3465ec49562
threadfin-0.1.2/ 0000775 0000000 0000000 00000000000 14370056340 0013460 5 ustar 00root root 0000000 0000000 threadfin-0.1.2/.editorconfig 0000664 0000000 0000000 00000000225 14370056340 0016134 0 ustar 00root root 0000000 0000000 [*]
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{yaml,yml}]
indent_size = 2
threadfin-0.1.2/.github/ 0000775 0000000 0000000 00000000000 14370056340 0015020 5 ustar 00root root 0000000 0000000 threadfin-0.1.2/.github/dependabot.yml 0000664 0000000 0000000 00000000221 14370056340 0017643 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: daily
time: "11:00"
open-pull-requests-limit: 10
threadfin-0.1.2/.github/release-drafter.yml 0000664 0000000 0000000 00000000514 14370056340 0020610 0 ustar 00root root 0000000 0000000 categories:
- title: "Security"
label: security
- title: "Added"
labels:
- feature
- enhancement
- title: "Fixed"
label: bug
- title: "Dependency Updates"
label: dependencies
change-template: '- $TITLE (#$NUMBER) @$AUTHOR'
no-changes-template: '- No changes'
template: |
## Changed
$CHANGES
threadfin-0.1.2/.github/workflows/ 0000775 0000000 0000000 00000000000 14370056340 0017055 5 ustar 00root root 0000000 0000000 threadfin-0.1.2/.github/workflows/ci.yml 0000664 0000000 0000000 00000002425 14370056340 0020176 0 ustar 00root root 0000000 0000000 name: ci
on:
push:
branches: [master]
pull_request:
jobs:
test:
strategy:
matrix:
include:
- runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
- runner: macos-11
target: x86_64-apple-darwin
- runner: windows-latest
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.runner }}
timeout-minutes: 10
env:
RUST_BACKTRACE: 1
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: "1.46.0"
target: ${{ matrix.target }}
default: true
- run: cargo test --target ${{ matrix.target }}
cross-compile:
strategy:
matrix:
include:
- runner: ubuntu-latest
target: armv5te-unknown-linux-gnueabi
- runner: ubuntu-latest
target: mipsel-unknown-linux-gnu
runs-on: ${{ matrix.runner }}
timeout-minutes: 10
env:
RUST_BACKTRACE: 1
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: "1.46.0"
target: ${{ matrix.target }}
default: true
- run: cargo build --target ${{ matrix.target }}
threadfin-0.1.2/.github/workflows/release-management.yaml 0000664 0000000 0000000 00000000356 14370056340 0023477 0 ustar 00root root 0000000 0000000 name: release management
on:
push:
branches: [master]
jobs:
update-draft-release:
runs-on: ubuntu-latest
steps:
- uses: toolmantim/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
threadfin-0.1.2/.github/workflows/release.yml 0000664 0000000 0000000 00000000461 14370056340 0021221 0 ustar 00root root 0000000 0000000 name: release
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Publish to crates.io
run: cargo publish --token "${CARGO_TOKEN}" --no-verify
env:
CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }}
threadfin-0.1.2/.github/workflows/sponsors.yml 0000664 0000000 0000000 00000001161 14370056340 0021465 0 ustar 00root root 0000000 0000000 name: Update Sponsors README
on:
schedule:
- cron: '42 3 */2 * *'
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: JamesIves/github-sponsors-readme-action@1.0.5
with:
token: ${{ secrets.SPONSORS_PAT }}
minimum: 1000
file: 'README.md'
template: >-
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Update sponsors list in README
threadfin-0.1.2/.gitignore 0000664 0000000 0000000 00000000023 14370056340 0015443 0 ustar 00root root 0000000 0000000 /target
Cargo.lock
threadfin-0.1.2/Cargo.toml 0000664 0000000 0000000 00000001155 14370056340 0015412 0 ustar 00root root 0000000 0000000 [package]
name = "threadfin"
version = "0.1.2"
description = "A thread pool for running multiple tasks on a configurable group of threads."
authors = ["Stephen M. Coakley "]
license = "MIT"
keywords = ["threadpool", "thread", "pool", "parallel", "async"]
categories = ["concurrency"]
repository = "https://github.com/sagebind/threadfin"
documentation = "https://docs.rs/threadfin/"
readme = "README.md"
edition = "2018"
[dependencies]
crossbeam-channel = "0.5"
num_cpus = "1"
once_cell = ">=1.0, <=1.14"
waker-fn = "1"
[dev-dependencies]
futures-timer = "3"
[workspace]
members = ["benchmarks"]
threadfin-0.1.2/LICENSE 0000664 0000000 0000000 00000002063 14370056340 0014466 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2021 Stephen M. Coakley
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.
threadfin-0.1.2/README.md 0000664 0000000 0000000 00000004710 14370056340 0014741 0 ustar 00root root 0000000 0000000 # Threadfin
A thread pool for running multiple tasks on a configurable group of threads.
[](https://crates.io/crates/threadfin)
[](https://docs.rs/threadfin)
[](LICENSE)
[](#minimum-supported-rust-version)
[](https://github.com/sagebind/threadfin/actions)
Extra features:
- Dynamic pool size based on load
- Support for async tasks
- Tasks return a handle which can be joined or awaited for the return value
- Optional common process-wide thread pool
## Async support
Threadfin supports asynchronous usage via futures, and allows you to mix and match both synchronous and asynchronous tasks within a single thread pool.
## Examples
```rust
// Create a new pool.
let pool = threadfin::builder().size(8).build();
// Schedule some work.
let compute_task = pool.execute(|| {
// Some expensive computation
2 + 2
});
// Do something in the meantime.
println!("Waiting for result...");
// Wait for the task to complete and get the result.
let sum = compute_task.join();
println!("Sum: 2 + 2 = {}", sum);
```
## Installation
Install via Cargo by adding to your Cargo.toml file:
```toml
[dependencies]
threadfin = "0.1"
```
### Minimum supported Rust version
The minimum supported Rust version (or MSRV) for Threadfin is stable Rust 1.46 or greater, meaning we only guarantee that Threadfin will compile if you use a rustc version of at least 1.46. It might compile with older versions but that could change at any time.
This version is explicitly tested in CI and may only be bumped in new minor versions. Any changes to the supported minimum version will be called out in the release notes.
## Other libraries
- [threadpool](https://github.com/rust-threadpool/rust-threadpool)
- [scoped_threadpool](https://github.com/kimundi/scoped-threadpool-rs)
- [rusty_pool](https://github.com/robinfriedli/rusty_pool)
- [rayon](https://github.com/rayon-rs/rayon)
## Sponsors
Special thanks to sponsors of my open-source work!
## License
Licensed under the MIT license. See the [LICENSE](LICENSE) file for details.
threadfin-0.1.2/benchmarks/ 0000775 0000000 0000000 00000000000 14370056340 0015575 5 ustar 00root root 0000000 0000000 threadfin-0.1.2/benchmarks/Cargo.toml 0000664 0000000 0000000 00000000474 14370056340 0017532 0 ustar 00root root 0000000 0000000 [package]
name = "threadfin-benchmarks"
version = "0.0.0"
authors = ["Stephen M. Coakley "]
license = "MIT"
edition = "2018"
[dependencies.threadfin]
path = ".."
[dev-dependencies]
criterion = "0.3"
num_cpus = "1"
rusty_pool = "0.7"
threadpool = "1"
[[bench]]
name = "pool"
harness = false
threadfin-0.1.2/benchmarks/benches/ 0000775 0000000 0000000 00000000000 14370056340 0017204 5 ustar 00root root 0000000 0000000 threadfin-0.1.2/benchmarks/benches/pool.rs 0000664 0000000 0000000 00000003041 14370056340 0020521 0 ustar 00root root 0000000 0000000 use criterion::*;
fn criterion_benchmark(c: &mut Criterion) {
let threads = num_cpus::get().max(1);
let tasks = 1000;
let mut group = c.benchmark_group("pool");
group.sample_size(10);
group.bench_function("threadfin", |b| {
b.iter_batched(
|| threadfin::ThreadPool::builder().size(threads).build(),
|pool| {
for _ in 0..tasks {
pool.execute(|| {
let _ = black_box(8 + 9);
});
}
pool.join();
},
BatchSize::LargeInput,
);
});
group.bench_function("threadpool", |b| {
b.iter_batched(
|| threadpool::ThreadPool::new(threads),
|pool| {
for _ in 0..tasks {
pool.execute(|| {
let _ = black_box(8 + 9);
});
}
pool.join();
},
BatchSize::LargeInput,
);
});
group.bench_function("rusty_pool", |b| {
b.iter_batched(
|| rusty_pool::ThreadPool::new(threads, threads, std::time::Duration::ZERO),
|pool| {
for _ in 0..tasks {
pool.execute(|| {
let _ = black_box(8 + 9);
});
}
pool.shutdown_join();
},
BatchSize::LargeInput,
);
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
threadfin-0.1.2/benchmarks/src/ 0000775 0000000 0000000 00000000000 14370056340 0016364 5 ustar 00root root 0000000 0000000 threadfin-0.1.2/benchmarks/src/lib.rs 0000664 0000000 0000000 00000000000 14370056340 0017466 0 ustar 00root root 0000000 0000000 threadfin-0.1.2/build.rs 0000664 0000000 0000000 00000000656 14370056340 0015134 0 ustar 00root root 0000000 0000000 use std::env;
fn main() {
let target = env::var("TARGET").unwrap();
if target.starts_with("x86_64")
|| target.starts_with("i686")
|| target.starts_with("aarch64")
|| target.starts_with("powerpc64")
|| target.starts_with("sparc64")
|| target.starts_with("mips64el")
|| target.starts_with("riscv64")
{
println!("cargo:rustc-cfg=threadfin_has_atomic64");
}
}
threadfin-0.1.2/rustfmt.toml 0000664 0000000 0000000 00000000272 14370056340 0016062 0 ustar 00root root 0000000 0000000 edition = "2018"
imports_layout = "HorizontalVertical"
merge_imports = true
overflow_delimited_expr = true
struct_lit_single_line = false
use_field_init_shorthand = true
version = "Two"
threadfin-0.1.2/src/ 0000775 0000000 0000000 00000000000 14370056340 0014247 5 ustar 00root root 0000000 0000000 threadfin-0.1.2/src/common.rs 0000664 0000000 0000000 00000003112 14370056340 0016102 0 ustar 00root root 0000000 0000000 use crate::{Builder, CommonAlreadyInitializedError, ThreadPool};
use once_cell::sync::OnceCell;
static COMMON: OnceCell = OnceCell::new();
/// Get a shared reference to a common thread pool for the entire process.
///
/// # Examples
///
/// ```
/// let result = threadfin::common().execute(|| 2 + 2).join();
///
/// assert_eq!(result, 4);
/// ```
pub fn common() -> &'static ThreadPool {
COMMON.get_or_init(|| common_builder().build())
}
/// Configure the common thread pool.
///
/// This should be done near the start of your program before any other code
/// uses the common pool, as this function will return an error if the common
/// pool has already been initialized.
///
/// Only programs should use this function! Libraries should not use this
/// function and instead allow the running program to configure the common pool.
/// If you need a customized pool in a library then you should use a separate
/// pool instance.
///
/// # Examples
///
/// ```
/// threadfin::configure_common(|builder| builder
/// .size(3)
/// .queue_limit(1024))
/// .unwrap();
///
/// assert_eq!(threadfin::common().threads(), 3);
/// ```
pub fn configure_common(f: F) -> Result<(), CommonAlreadyInitializedError>
where
F: FnOnce(Builder) -> Builder,
{
let mut was_initialized = true;
COMMON.get_or_init(|| {
was_initialized = false;
f(common_builder()).build()
});
if was_initialized {
Err(CommonAlreadyInitializedError::new())
} else {
Ok(())
}
}
fn common_builder() -> Builder {
Builder::default().name("common-pool")
}
threadfin-0.1.2/src/error.rs 0000664 0000000 0000000 00000002715 14370056340 0015753 0 ustar 00root root 0000000 0000000 use std::{error::Error, fmt};
/// An error returned when a task could not be executed because a thread pool
/// was full.
///
/// Contains the original task that failed to be submitted. This allows you to
/// try the submission again later or take some other action.
pub struct PoolFullError(pub(crate) T);
impl PoolFullError {
/// Extracts the inner task that could not be executed.
pub fn into_inner(self) -> T {
self.0
}
}
impl Error for PoolFullError {}
impl fmt::Debug for PoolFullError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("PoolFullError(..)")
}
}
impl fmt::Display for PoolFullError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("thread pool is full")
}
}
/// An error returned when attempting to configure the common thread pool after
/// it has already been initialized.
pub struct CommonAlreadyInitializedError(());
impl CommonAlreadyInitializedError {
pub(crate) fn new() -> Self {
Self(())
}
}
impl Error for CommonAlreadyInitializedError {}
impl fmt::Debug for CommonAlreadyInitializedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CommonAlreadyInitializedError")
}
}
impl fmt::Display for CommonAlreadyInitializedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("common thread pool already initialized")
}
}
threadfin-0.1.2/src/lib.rs 0000664 0000000 0000000 00000002455 14370056340 0015371 0 ustar 00root root 0000000 0000000 //! A thread pool for running multiple tasks on a configurable group of threads.
//!
//! Extra features:
//!
//! - Dynamic pool size based on load
//! - Support for async tasks
//! - Tasks return a handle which can be joined or awaited for the return value
//! - Optional common process-wide thread pool
//!
//! ## Async support
//!
//! Threadfin supports asynchronous usage via futures, and allows you to mix and
//! match both synchronous and asynchronous tasks within a single thread pool.
//!
//! ## Examples
//!
//! ```
//! // Create a new pool.
//! let pool = threadfin::builder().size(8).build();
//!
//! // Schedule some work.
//! let compute_task = pool.execute(|| {
//! // Some expensive computation
//! 2 + 2
//! });
//!
//! // Do something in the meantime.
//! println!("Waiting for result...");
//!
//! // Wait for the task to complete and get the result.
//! let sum = compute_task.join();
//! println!("Sum: 2 + 2 = {}", sum);
//! ```
mod common;
mod error;
mod pool;
mod task;
mod wakers;
mod worker;
pub use crate::{
common::*,
error::*,
pool::{Builder, PerCore, SizeConstraint, ThreadPool},
task::Task,
};
/// Get a builder for creating a customized thread pool.
///
/// A shorthand for [`ThreadPool::builder`].
#[inline]
pub fn builder() -> Builder {
ThreadPool::builder()
}
threadfin-0.1.2/src/pool.rs 0000664 0000000 0000000 00000067406 14370056340 0015603 0 ustar 00root root 0000000 0000000 //! Implementation of the thread pool itself.
use std::{
fmt,
future::Future,
ops::{Range, RangeInclusive, RangeTo, RangeToInclusive},
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
Condvar,
Mutex,
},
thread,
time::{Duration, Instant},
};
use crossbeam_channel::{bounded, unbounded, Receiver, Sender};
use once_cell::sync::Lazy;
use crate::{
error::PoolFullError,
task::{Coroutine, Task},
worker::{Listener, Worker},
};
#[cfg(threadfin_has_atomic64)]
type AtomicCounter = std::sync::atomic::AtomicU64;
#[cfg(not(threadfin_has_atomic64))]
type AtomicCounter = std::sync::atomic::AtomicU32;
/// A value describing a size constraint for a thread pool.
///
/// Any size constraint can be wrapped in [`PerCore`] to be made relative to the
/// number of available CPU cores on the current system.
///
/// See [`Builder::size`] for details.
pub trait SizeConstraint {
/// Get the minimum number of threads to be in the thread pool.
fn min(&self) -> usize;
/// Get the maximum number of threads to be in the thread pool.
fn max(&self) -> usize;
}
impl SizeConstraint for usize {
fn min(&self) -> usize {
*self
}
fn max(&self) -> usize {
*self
}
}
impl SizeConstraint for Range {
fn min(&self) -> usize {
self.start
}
fn max(&self) -> usize {
self.end
}
}
impl SizeConstraint for RangeInclusive {
fn min(&self) -> usize {
*self.start()
}
fn max(&self) -> usize {
*self.end()
}
}
impl SizeConstraint for RangeTo {
fn min(&self) -> usize {
0
}
fn max(&self) -> usize {
self.end
}
}
impl SizeConstraint for RangeToInclusive {
fn min(&self) -> usize {
0
}
fn max(&self) -> usize {
self.end
}
}
/// Modifies a size constraint to be per available CPU core.
///
/// # Examples
///
/// ```
/// # use threadfin::PerCore;
/// // one thread per core
/// let size = PerCore(1);
///
/// // four threads per core
/// let size = PerCore(4);
///
/// // at least 1 thread per core and at most 2 threads per core
/// let size = PerCore(1..2);
/// ```
pub struct PerCore(pub T);
static CORE_COUNT: Lazy = Lazy::new(|| num_cpus::get().max(1));
impl From for PerCore {
fn from(size: T) -> Self {
Self(size)
}
}
impl SizeConstraint for PerCore {
fn min(&self) -> usize {
*CORE_COUNT * self.0.min()
}
fn max(&self) -> usize {
*CORE_COUNT * self.0.max()
}
}
/// A builder for constructing a customized [`ThreadPool`].
///
/// # Examples
///
/// ```
/// let custom_pool = threadfin::builder()
/// .name("my-pool")
/// .size(2)
/// .build();
/// ```
#[derive(Debug)]
pub struct Builder {
name: Option,
size: Option<(usize, usize)>,
stack_size: Option,
queue_limit: Option,
worker_concurrency_limit: usize,
keep_alive: Duration,
}
impl Default for Builder {
fn default() -> Self {
Self {
name: None,
size: None,
stack_size: None,
queue_limit: None,
worker_concurrency_limit: 16,
keep_alive: Duration::from_secs(60),
}
}
}
impl Builder {
/// Set a custom thread name for threads spawned by this thread pool.
///
/// # Panics
///
/// Panics if the name contains null bytes (`\0`).
///
/// # Examples
///
/// ```
/// let pool = threadfin::builder().name("my-pool").build();
/// ```
pub fn name>(mut self, name: T) -> Self {
let name = name.into();
if name.as_bytes().contains(&0) {
panic!("thread pool name must not contain null bytes");
}
self.name = Some(name);
self
}
/// Set the number of threads to be managed by this thread pool.
///
/// If a `usize` is supplied, the pool will have a fixed number of threads.
/// If a range is supplied, the lower bound will be the core pool size while
/// the upper bound will be a maximum pool size the pool is allowed to burst
/// up to when the core threads are busy.
///
/// Any size constraint can be wrapped in [`PerCore`] to be made relative to
/// the number of available CPU cores on the current system.
///
/// If not set, a reasonable size will be selected based on the number of
/// CPU cores on the current system.
///
/// # Examples
///
/// ```
/// // Create a thread pool with exactly 2 threads.
/// let pool = threadfin::builder().size(2).build();
/// ```
///
/// ```
/// // Create a thread pool with no idle threads, but will spawn up to 4
/// // threads lazily when there's work to be done.
/// let pool = threadfin::builder().size(0..4).build();
///
/// // Or equivalently:
/// let pool = threadfin::builder().size(..4).build();
/// ```
///
/// ```
/// use threadfin::PerCore;
///
/// // Create a thread pool with two threads per core.
/// let pool = threadfin::builder().size(PerCore(2)).build();
/// ```
///
/// # Panics
///
/// Panics if an invalid range is supplied with a lower bound larger than
/// the upper bound, or if the upper bound is 0.
pub fn size(mut self, size: S) -> Self {
let (min, max) = (size.min(), size.max());
if min > max {
panic!("thread pool minimum size cannot be larger than maximum size");
}
if max == 0 {
panic!("thread pool maximum size must be non-zero");
}
self.size = Some((min, max));
self
}
/// Set the size of the stack (in bytes) for threads in this thread pool.
///
/// The actual stack size may be greater than this value if the platform
/// enforces a larger minimum stack size.
///
/// The stack size if not specified will be the default size for new Rust
/// threads, currently 2 MiB. This can also be overridden by setting the
/// `RUST_MIN_STACK` environment variable if not specified in code.
///
/// # Examples
///
/// ```
/// // Worker threads will have a stack size of at least 32 KiB.
/// let pool = threadfin::builder().stack_size(32 * 1024).build();
/// ```
pub fn stack_size(mut self, size: usize) -> Self {
self.stack_size = Some(size);
self
}
/// Set a maximum number of pending tasks allowed to be submitted before
/// blocking.
///
/// If set to zero, queueing will be disabled and attempting to execute a
/// new task will block until an idle worker thread can immediately begin
/// executing the task or a new worker thread can be created to execute the
/// task.
///
/// If not set, no limit is enforced.
pub fn queue_limit(mut self, limit: usize) -> Self {
self.queue_limit = Some(limit);
self
}
/// Set a duration for how long to keep idle worker threads alive.
///
/// If the pool has more than the minimum configured number of threads and
/// threads remain idle for more than this duration, they will be terminated
/// until the minimum thread count is reached.
pub fn keep_alive(mut self, duration: Duration) -> Self {
self.keep_alive = duration;
self
}
/// Set a limit on the number of concurrent tasks that can be run by a
/// single worker thread.
///
/// When executing asynchronous tasks, if the underlying future being
/// executed yields, that worker thread can begin working on new tasks
/// concurrently while waiting on the prior task to resume. This allows for
/// a primitive M:N scheduling model that supports running significantly
/// more futures concurrently than the number of threads in the thread pool.
///
/// To prevent a worker thread from over-committing to too many tasks at
/// once (which could result in extra latency if a task wakes but its
/// assigned worker is too busy with other tasks) worker threads limit
/// themselves to a maximum number of concurrent tasks. This method allows
/// you to customize that limit.
///
/// The default limit if not specified is 16.
pub fn worker_concurrency_limit(mut self, limit: usize) -> Self {
self.worker_concurrency_limit = limit;
self
}
/// Create a thread pool according to the configuration set with this
/// builder.
pub fn build(self) -> ThreadPool {
let size = self.size.unwrap_or_else(|| {
let size = PerCore(1..2);
(size.min(), size.max())
});
let shared = Shared {
min_threads: size.0,
max_threads: size.1,
thread_count: Default::default(),
running_tasks_count: Default::default(),
completed_tasks_count: Default::default(),
panicked_tasks_count: Default::default(),
keep_alive: self.keep_alive,
shutdown_cvar: Condvar::new(),
};
let pool = ThreadPool {
thread_name: self.name,
stack_size: self.stack_size,
concurrency_limit: self.worker_concurrency_limit,
queue: self.queue_limit.map(bounded).unwrap_or_else(unbounded),
immediate_queue: bounded(0),
shared: Arc::new(shared),
};
for _ in 0..size.0 {
let result = pool.spawn_thread(None);
assert!(result.is_ok());
}
pool
}
}
/// A thread pool for running multiple tasks on a configurable group of threads.
///
/// Thread pools can improve performance when executing a large number of
/// concurrent tasks since the expensive overhead of spawning threads is
/// minimized as threads are re-used for multiple tasks. Thread pools are also
/// useful for controlling and limiting parallelism.
///
/// Dropping the thread pool will prevent any further tasks from being scheduled
/// on the pool and detaches all threads in the pool. If you want to block until
/// all pending tasks have completed and the pool is entirely shut down, then
/// use one of the available [`join`](ThreadPool::join) methods.
///
/// # Pool size
///
/// Every thread pool has a minimum and maximum number of worker threads that it
/// will spawn for executing tasks. This range is known as the _pool size_, and
/// affects pool behavior in the following ways:
///
/// - **Minimum size**: A guaranteed number of threads that will always be
/// created and maintained by the thread pool. Threads will be eagerly created
/// to meet this minimum size when the pool is created, and at least this many
/// threads will be kept running in the pool until the pool is shut down.
/// - **Maximum size**: A limit on the number of additional threads to spawn to
/// execute more work.
///
/// # Queueing
///
/// If a new or existing worker thread is unable to immediately start processing
/// a submitted task, that task will be placed in a queue for worker threads to
/// take from when they complete their current tasks. Queueing is only used when
/// it is not possible to directly handoff a task to an existing thread and
/// spawning a new thread would exceed the pool's configured maximum size.
///
/// By default, thread pools are configured to use an _unbounded_ queue which
/// can hold an unlimited number of pending tasks. This is a sensible default,
/// but is not desirable in all use-cases and can be changed with
/// [`Builder::queue_limit`].
///
/// # Monitoring
///
/// Each pool instance provides methods for gathering various statistics on the
/// pool's usage, such as number of current number of threads, tasks completed
/// over time, and queued tasks. While these methods provide the most up-to-date
/// numbers upon invocation, they should not be used for controlling program
/// behavior since they can become immediately outdated due to the live nature
/// of the pool.
pub struct ThreadPool {
thread_name: Option,
stack_size: Option,
concurrency_limit: usize,
queue: (Sender, Receiver),
immediate_queue: (Sender, Receiver),
shared: Arc,
}
impl Default for ThreadPool {
fn default() -> Self {
Self::new()
}
}
impl ThreadPool {
/// Create a new thread pool with the default configuration.
///
/// If you'd like to customize the thread pool's behavior then use
/// [`ThreadPool::builder`].
#[inline]
pub fn new() -> Self {
Self::builder().build()
}
/// Get a builder for creating a customized thread pool.
#[inline]
pub fn builder() -> Builder {
Builder::default()
}
/// Get the number of threads currently running in the thread pool.
pub fn threads(&self) -> usize {
*self.shared.thread_count.lock().unwrap()
}
/// Get the number of tasks queued for execution, but not yet started.
///
/// This number will always be less than or equal to the configured
/// [`queue_limit`](Builder::queue_limit), if any.
///
/// Note that the number returned may become immediately outdated after
/// invocation.
///
/// # Examples
///
/// ```
/// use std::{thread::sleep, time::Duration};
///
/// // Create a pool with just one thread.
/// let pool = threadfin::builder().size(1).build();
///
/// // Nothing is queued yet.
/// assert_eq!(pool.queued_tasks(), 0);
///
/// // Start a slow task.
/// let task = pool.execute(|| {
/// sleep(Duration::from_millis(100));
/// });
///
/// // Wait a little for the task to start.
/// sleep(Duration::from_millis(10));
/// assert_eq!(pool.queued_tasks(), 0);
///
/// // Enqueue some more tasks.
/// let count = 4;
/// for _ in 0..count {
/// pool.execute(|| {
/// // work to do
/// });
/// }
///
/// // The tasks should still be in the queue because the slow task is
/// // running on the only thread.
/// assert_eq!(pool.queued_tasks(), count);
/// # pool.join();
/// ```
#[inline]
pub fn queued_tasks(&self) -> usize {
self.queue.0.len()
}
/// Get the number of tasks currently running.
///
/// Note that the number returned may become immediately outdated after
/// invocation.
///
/// # Examples
///
/// ```
/// use std::{thread::sleep, time::Duration};
///
/// let pool = threadfin::ThreadPool::new();
///
/// // Nothing is running yet.
/// assert_eq!(pool.running_tasks(), 0);
///
/// // Start a task.
/// let task = pool.execute(|| {
/// sleep(Duration::from_millis(100));
/// });
///
/// // Wait a little for the task to start.
/// sleep(Duration::from_millis(10));
/// assert_eq!(pool.running_tasks(), 1);
///
/// // Wait for the task to complete.
/// task.join();
/// assert_eq!(pool.running_tasks(), 0);
/// ```
#[inline]
pub fn running_tasks(&self) -> usize {
self.shared.running_tasks_count.load(Ordering::Relaxed)
}
/// Get the number of tasks completed (successfully or otherwise) by this
/// pool since it was created.
///
/// Note that the number returned may become immediately outdated after
/// invocation.
///
/// # Examples
///
/// ```
/// let pool = threadfin::ThreadPool::new();
/// assert_eq!(pool.completed_tasks(), 0);
///
/// pool.execute(|| 2 + 2).join();
/// assert_eq!(pool.completed_tasks(), 1);
///
/// pool.execute(|| 2 + 2).join();
/// assert_eq!(pool.completed_tasks(), 2);
/// ```
#[inline]
#[allow(clippy::useless_conversion)]
pub fn completed_tasks(&self) -> u64 {
self.shared.completed_tasks_count.load(Ordering::Relaxed).into()
}
/// Get the number of tasks that have panicked since the pool was created.
///
/// Note that the number returned may become immediately outdated after
/// invocation.
///
/// # Examples
///
/// ```
/// use std::{thread::sleep, time::Duration};
///
/// let pool = threadfin::ThreadPool::new();
/// assert_eq!(pool.panicked_tasks(), 0);
///
/// let task = pool.execute(|| {
/// panic!("this task panics");
/// });
///
/// while !task.is_done() {
/// sleep(Duration::from_millis(100));
/// }
///
/// assert_eq!(pool.panicked_tasks(), 1);
/// ```
#[inline]
#[allow(clippy::useless_conversion)]
pub fn panicked_tasks(&self) -> u64 {
self.shared.panicked_tasks_count.load(Ordering::SeqCst).into()
}
/// Submit a closure to be executed by the thread pool.
///
/// If all worker threads are busy, but there are less threads than the
/// configured maximum, an additional thread will be created and added to
/// the pool to execute this task.
///
/// If all worker threads are busy and the pool has reached the configured
/// maximum number of threads, the task will be enqueued. If the queue is
/// configured with a limit, this call will block until space becomes
/// available in the queue.
///
/// # Examples
///
/// ```
/// let pool = threadfin::ThreadPool::new();
/// let task = pool.execute(|| {
/// 2 + 2 // some expensive computation
/// });
///
/// // do something in the meantime
///
/// // now wait for the result
/// let sum = task.join();
/// assert_eq!(sum, 4);
/// ```
pub fn execute(&self, closure: F) -> Task
where
T: Send + 'static,
F: FnOnce() -> T + Send + 'static,
{
let (task, coroutine) = Task::from_closure(closure);
self.execute_coroutine(coroutine);
task
}
/// Submit a future to be executed by the thread pool.
///
/// If all worker threads are busy, but there are less threads than the
/// configured maximum, an additional thread will be created and added to
/// the pool to execute this task.
///
/// If all worker threads are busy and the pool has reached the configured
/// maximum number of threads, the task will be enqueued. If the queue is
/// configured with a limit, this call will block until space becomes
/// available in the queue.
///
/// # Thread locality
///
/// While the given future must implement [`Send`] to be moved into a thread
/// in the pool to be processed, once the future is assigned a thread it
/// will stay assigned to that single thread until completion. This improves
/// cache locality even across `.await` points in the future.
///
/// ```
/// let pool = threadfin::ThreadPool::new();
/// let task = pool.execute_future(async {
/// 2 + 2 // some asynchronous code
/// });
///
/// // do something in the meantime
///
/// // now wait for the result
/// let sum = task.join();
/// assert_eq!(sum, 4);
/// ```
pub fn execute_future(&self, future: F) -> Task
where
T: Send + 'static,
F: Future