orx_parallel/pools/pool.rs
1#[cfg(any(feature = "std", feature = "rayon-core"))]
2use crate::NumThreads;
3#[cfg(feature = "std")]
4use crate::pools::pool_impl::{BasicPool, OncePool};
5use crate::pools::{DefaultPool, global_pool};
6
7/// Factory for creating thread pools with different characteristics.
8///
9/// `Pool` provides builder methods to create various types of thread pools that can be used
10/// for parallel computations. Each pool type has different properties regarding thread lifecycle
11/// and persistence.
12///
13/// > **Note:** `Pool` is a convenience factory for thread pools provided or adapted by this crate.
14/// > You can also implement [`ThreadPool`](crate::ThreadPool) yourself and pass it directly
15/// > to `.pool(...)` or to runner constructors that accept any thread pool implementing the trait.
16///
17/// # Thread Count Configuration
18///
19/// When creating a pool, the thread count is determined by combining:
20///
21/// 1. **Requested count** - Passed to factory methods
22/// 2. **Environment limit** - `ORX_NUM_THREADS` if set
23/// 3. **System availability** - Number of logical CPUs available
24///
25/// The pool will use the minimum of these constraints.
26///
27/// # Examples
28///
29/// ```ignore
30/// use orx_parallel::*;
31///
32/// // Create a OncePool with auto-detection (subject to ORX_NUM_THREADS)
33/// let pool = Pool::once(NumThreads::Auto);
34///
35/// // Create a OncePool capped at 4 threads
36/// let pool = Pool::once(4); // Converted from usize via From impl
37///
38/// // Create a persistent BasicPool with 8 threads
39/// let pool = Pool::basic(8);
40///
41/// // Create a Rayon pool (requires rayon-core feature)
42/// let pool = Pool::rayon(NumThreads::Auto)?;
43/// ```
44///
45/// # Pool Types
46///
47/// - **OncePool** (with `transient-pool` feature) - Spawns threads only when needed, releases after computation
48/// - **BasicPool** (default) - Maintains persistent workers across multiple computations
49/// - **Rayon** - Uses the Rayon parallel runtime (external crate)
50///
51/// See the [`thread_usage.md`](https://github.com/orxfun/orx-parallel/blob/main/docs/thread_usage.md) documentation for complete details.
52pub struct Pool;
53
54impl Pool {
55 /// Returns the default global thread pool.
56 ///
57 /// This exposes the thread pool's functionality directly, allowing convenient
58 /// **ad-hoc** parallel computation, on top of the parallel iterators of this
59 /// crate. Note, however, that such ad-hoc parallelization does not benefit from
60 /// the input concurrent iterator and parallel runner strategy optimizations that
61 /// parallel iterators build on. Therefore, it is best suited for a handful of
62 /// large enough, independent tasks rather than for computations with numerous
63 /// small tasks.
64 ///
65 /// There are two ways to use it:
66 ///
67 /// * Manually via [`scope`] and [`run`]:
68 ///
69 /// ```rust
70 /// use orx_parallel::*;
71 ///
72 /// Pool::global().scope(|s| {
73 /// s.run(|| println!("task A"));
74 /// s.run(|| println!("task B"));
75 /// });
76 /// ```
77 ///
78 /// * Or via [`tasks!`] and [`run_all`], which builds a statically typed queue of
79 /// tasks to be run in parallel:
80 ///
81 /// ```rust
82 /// use orx_parallel::*;
83 /// use std::sync::Mutex;
84 ///
85 /// let numbers = [4, 8, 15, 16, 23, 42];
86 ///
87 /// let sum = Mutex::new(0);
88 /// let max = Mutex::new(i32::MIN);
89 /// let all_positive = Mutex::new(false);
90 ///
91 /// let tasks = tasks![
92 /// || *sum.lock().unwrap() = numbers.iter().sum(),
93 /// || *max.lock().unwrap() = numbers.iter().copied().max().unwrap(),
94 /// || *all_positive.lock().unwrap() = numbers.iter().all(|&x| x > 0),
95 /// ];
96 ///
97 /// Pool::global().run_all(tasks);
98 ///
99 /// println!(
100 /// "sum={}, max={}, all_positive={}",
101 /// sum.into_inner().unwrap(),
102 /// max.into_inner().unwrap(),
103 /// all_positive.into_inner().unwrap(),
104 /// );
105 /// ```
106 ///
107 /// [`scope`]: crate::ThreadPool::scope
108 /// [`run`]: crate::Scope::run
109 /// [`run_all`]: crate::ThreadPool::run_all
110 /// [`tasks!`]: crate::tasks
111 pub fn global() -> DefaultPool {
112 global_pool()
113 }
114
115 /// Creates a lightweight on-demand pool with the specified thread configuration.
116 ///
117 /// A `OncePool` is a lightweight virtual pool that spawns worker threads just before
118 /// a computation starts and releases them immediately after. This reduces overhead when
119 /// a persistent thread pool isn't needed.
120 ///
121 /// # Thread Count Decision
122 ///
123 /// The actual thread count is determined by:
124 /// - The `num_threads` parameter
125 /// - The `ORX_NUM_THREADS` environment variable (if set)
126 /// - The number of available system CPU cores
127 ///
128 /// The minimum of these constraints will be used.
129 ///
130 /// # Parameters
131 ///
132 /// - `num_threads` - Either:
133 /// - `0` or `NumThreads::Auto` - Use all available threads (respecting constraints)
134 /// - `n > 0` or `NumThreads::Max(n)` - Cap at `n` threads (respecting constraints)
135 ///
136 /// # Examples
137 ///
138 /// ```ignore
139 /// use orx_parallel::*;
140 ///
141 /// // Auto-detect threads
142 /// let pool = Pool::once(NumThreads::Auto);
143 ///
144 /// // Cap at 4 threads
145 /// let pool = Pool::once(4);
146 ///
147 /// // Same as above (usize converts via From impl)
148 /// let pool = Pool::once(NumThreads::Max(std::num::NonZeroUsize::new(4).unwrap()));
149 /// ```
150 ///
151 /// # Default Behavior
152 ///
153 /// This is available when the `transient-pool` feature is enabled.
154 /// Applications can explicitly create an `OncePool` to configure custom thread settings
155 /// for on-demand thread spawning and cleanup.
156 #[cfg(feature = "std")]
157 pub fn once(num_threads: impl Into<NumThreads>) -> OncePool {
158 OncePool::new(num_threads)
159 }
160
161 /// Creates a [`BasicPool`] with the specified thread configuration.
162 ///
163 /// A `BasicPool` maintains persistent worker threads that remain alive across
164 /// multiple parallel computations. This is more efficient than `OncePool` when
165 /// running many parallel operations sequentially.
166 ///
167 /// # Thread Count Decision
168 ///
169 /// Thread count is determined the same way as [`Self::once`]:
170 /// - The `num_threads` parameter
171 /// - The `ORX_NUM_THREADS` environment variable (if set)
172 /// - Available system CPU cores
173 ///
174 /// The minimum of these constraints will be used.
175 ///
176 /// # Parameters
177 ///
178 /// - `num_threads` - Configuration as described in [`Self::once`]
179 ///
180 /// # Examples
181 ///
182 /// ```ignore
183 /// use orx_parallel::*;
184 ///
185 /// // Create and reuse a persistent pool
186 /// let pool = Pool::basic(8);
187 ///
188 /// for data in datasets {
189 /// let result = data.into_par()
190 /// .map(|x| process(x))
191 /// .pool(pool)
192 /// .collect();
193 /// }
194 /// ```
195 ///
196 /// # Benefits Over OncePool
197 ///
198 /// - Worker threads persist between computations
199 /// - Avoids overhead of repeated thread spawning
200 /// - Ideal for applications with many parallel tasks
201 #[cfg(feature = "std")]
202 pub fn basic(num_threads: impl Into<NumThreads>) -> BasicPool {
203 BasicPool::new(num_threads)
204 }
205
206 /// Creates a Rayon [`ThreadPool`](https://docs.rs/rayon-core/latest/rayon_core/struct.ThreadPool.html).
207 ///
208 /// This method integrates with the Rayon parallel runtime. Rayon pools can be used
209 /// with orx-parallel parallel iterators through the `.pool()` method.
210 ///
211 /// # Thread Count Decision
212 ///
213 /// Rayon's thread count is determined similarly to other pools:
214 /// - When `num_threads` is `0` or `NumThreads::Auto`:
215 /// - Rayon uses `RAYON_NUM_THREADS` environment variable if set
216 /// - Otherwise uses the number of logical CPUs
217 /// - When `num_threads` is `n > 0` or `NumThreads::Max(n)`:
218 /// - Rayon will start at most `n` threads
219 ///
220 /// Note: `ORX_NUM_THREADS` is not automatically applied to Rayon pools.
221 /// See Rayon documentation for its configuration options.
222 ///
223 /// # Parameters
224 ///
225 /// - `num_threads` - Configuration for the Rayon thread pool
226 ///
227 /// # Returns
228 ///
229 /// - `Ok(ThreadPool)` - Successfully created Rayon pool
230 /// - `Err(ThreadPoolBuildError)` - Failed to create pool (e.g., invalid configuration)
231 ///
232 /// # Examples
233 ///
234 /// ```ignore
235 /// use orx_parallel::*;
236 ///
237 /// // Create a Rayon pool with automatic thread detection
238 /// let pool = Pool::rayon(NumThreads::Auto)?;
239 ///
240 /// // Create a Rayon pool capped at 4 threads
241 /// let pool = Pool::rayon(4)?;
242 ///
243 /// let result = (0..1000)
244 /// .into_par()
245 /// .map(|x| x * 2)
246 /// .pool(pool)
247 /// .collect();
248 /// ```
249 ///
250 /// # Features
251 ///
252 /// Requires the `rayon-core` feature to be enabled.
253 ///
254 /// [`ThreadPool`]: https://docs.rs/rayon-core/latest/rayon_core/struct.ThreadPool.html
255 #[cfg(feature = "rayon-core")]
256 pub fn rayon(
257 num_threads: impl Into<NumThreads>,
258 ) -> Result<rayon_core::ThreadPool, rayon_core::ThreadPoolBuildError> {
259 let num_threads = match num_threads.into() {
260 NumThreads::Auto => 0,
261 NumThreads::Max(nt) => nt.into(),
262 };
263 rayon_core::ThreadPoolBuilder::new()
264 .num_threads(num_threads)
265 .build()
266 }
267}