include/boost/capy/ex/thread_pool.hpp

100.0% Lines (7/0/7) 100.0% List of functions (3/0/3)
thread_pool.hpp
f(x) Functions (3)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 // Copyright (c) 2026 Michael Vandeberg
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/capy
9 //
10
11 #ifndef BOOST_CAPY_EX_THREAD_POOL_HPP
12 #define BOOST_CAPY_EX_THREAD_POOL_HPP
13
14 #include <boost/capy/detail/config.hpp>
15 #include <boost/capy/continuation.hpp>
16 #include <coroutine>
17 #include <boost/capy/ex/execution_context.hpp>
18 #include <cstddef>
19 #include <string_view>
20
21 namespace boost {
22 namespace capy {
23
24 /** Distributes posted work across a fixed group of worker threads via a shared queue.
25
26 Use this when you need to run coroutines on multiple threads
27 without the overhead of creating and destroying threads for
28 each task. Work items are distributed across the pool using
29 a shared queue.
30
31 @par Thread Safety
32 Distinct objects: Safe.
33 Shared objects: Safe for @ref get_executor, @ref join, and
34 @ref stop. Unsafe for construction and destruction.
35
36 @par Example
37 @par !example example
38
39
40 @note `join()` waits only for work that holds outstanding-work
41 counting, which `run_async` (and `make_work_guard`) provide. A bare
42 `executor_type::post()` does not register outstanding work, so
43 `join()` does not wait for it.
44 */
45 class BOOST_CAPY_DECL
46 thread_pool
47 : public execution_context
48 {
49 class impl;
50 impl* impl_;
51
52 public:
53 class executor_type;
54
55 /** Destroy the thread pool.
56
57 Signals all worker threads to stop, waits for them to
58 finish, and destroys any pending work items.
59
60 @pre No thread outside this pool may post or dispatch work to it
61 (or to a strand built on it) concurrently with, or after,
62 destruction. Doing so is undefined behavior. Submit such work
63 through @ref run_async or @ref run and call @ref join before
64 the pool is destroyed, so it has completed first.
65 */
66 ~thread_pool();
67
68 /** Construct a thread pool.
69
70 Records the requested worker count; no threads are created
71 yet. Threads start lazily on the executor's first `post()`.
72 If `num_threads` is zero, the number of threads is set to
73 the hardware concurrency, or one if that cannot be determined.
74
75 @param num_threads The number of worker threads, or zero
76 for automatic selection.
77
78 @param thread_name_prefix The prefix for worker thread names.
79 Thread names appear as "{prefix}0", "{prefix}1", etc.
80 The prefix is truncated to 12 characters. Defaults to
81 "capy-pool-".
82 */
83 explicit
84 thread_pool(
85 std::size_t num_threads = 0,
86 std::string_view thread_name_prefix = "capy-pool-");
87
88 /** Copy construction is disabled; a pool owns its worker threads.
89
90 @param other The pool that would be copied.
91 */
92 thread_pool(thread_pool const& other) = delete;
93
94 /** Copy assignment is disabled; a pool owns its worker threads.
95
96 @param other The pool that would be assigned from.
97
98 @return A reference to `*this`.
99 */
100 thread_pool& operator=(thread_pool const& other) = delete;
101
102 /** Wait for all outstanding work to complete.
103
104 Releases the internal work guard, then blocks the calling
105 thread until all outstanding work tracked by
106 @ref executor_type::on_work_started and
107 @ref executor_type::on_work_finished completes. After all
108 work finishes, joins the worker threads.
109
110 If @ref stop is called while `join()` is blocking, the
111 pool stops without waiting for remaining work to
112 complete. Worker threads finish their current item and
113 exit; `join()` still waits for all threads to be joined
114 before returning.
115
116 This function is idempotent. The first call performs the
117 join; subsequent calls return immediately.
118
119 @pre Must not be called from a thread in this pool (undefined
120 behavior).
121
122 @par Postconditions
123 All worker threads have been joined. The pool cannot be
124 reused.
125
126 @par Thread Safety
127 May be called from any thread not in this pool.
128 */
129 void
130 join() noexcept;
131
132 /** Request all worker threads to stop.
133
134 Signals all threads to exit after finishing their current
135 work item. Queued work that has not started is abandoned.
136 Does not wait for threads to exit.
137
138 If @ref join is blocking on another thread, calling
139 `stop()` causes it to stop waiting for outstanding
140 work. The `join()` call still waits for worker threads
141 to finish their current item and exit before returning.
142
143 @par Thread Safety
144 May be called concurrently from any thread, including a
145 thread in this pool.
146 */
147 void
148 stop() noexcept;
149
150 /** Return an executor for this thread pool.
151
152 @return An executor associated with this thread pool.
153 */
154 executor_type
155 get_executor() const noexcept;
156 };
157
158 /** An executor that submits work to a thread_pool.
159
160 Executors are lightweight handles that can be copied and stored.
161 All copies refer to the same underlying thread pool.
162
163 @par Thread Safety
164 Distinct objects: Safe.
165 Shared objects: Safe.
166 */
167 class thread_pool::executor_type
168 {
169 friend class thread_pool;
170
171 thread_pool* pool_ = nullptr;
172
173 explicit
174 11809x executor_type(thread_pool& pool) noexcept
175 11809x : pool_(&pool)
176 {
177 11809x }
178
179 public:
180 /** Construct a default null executor.
181
182 The resulting executor is not associated with any pool.
183 `context()`, `dispatch()`, and `post()` require the
184 executor to be associated with a pool before use.
185 */
186 executor_type() = default;
187
188 /** Return the underlying thread pool.
189
190 @return A reference to the associated pool. The behavior is
191 undefined if the executor is not associated with a pool.
192 */
193 thread_pool&
194 12113x context() const noexcept
195 {
196 12113x return *pool_;
197 }
198
199 /** Notify that work has started.
200
201 Increments the outstanding work count. Must be paired
202 with a subsequent call to @ref on_work_finished.
203
204 @see on_work_finished, work_guard
205 */
206 BOOST_CAPY_DECL
207 void
208 on_work_started() const noexcept;
209
210 /** Notify that work has finished.
211
212 Decrements the outstanding work count. When the count
213 reaches zero after @ref thread_pool::join is called,
214 the pool's worker threads are signaled to stop.
215
216 @pre A preceding call to @ref on_work_started was made.
217
218 @see on_work_started, work_guard
219 */
220 BOOST_CAPY_DECL
221 void
222 on_work_finished() const noexcept;
223
224 /** Dispatch a continuation for execution.
225
226 If the calling thread is a worker of this pool, returns
227 `c.h` for symmetric transfer so the caller can resume the
228 continuation inline. Otherwise, posts the continuation to
229 the pool for execution on a worker thread and returns
230 `std::noop_coroutine()`.
231
232 @param c The continuation to execute. On the post path,
233 must remain at a stable address until dequeued
234 and resumed.
235
236 @return `c.h` when the calling thread is a pool worker;
237 `std::noop_coroutine()` otherwise.
238 */
239 BOOST_CAPY_DECL
240 std::coroutine_handle<>
241 dispatch(continuation& c) const;
242
243 /** Post a continuation to the thread pool.
244
245 The continuation is resumed on one of the pool's
246 worker threads. The continuation must remain at a stable
247 address until it is dequeued and resumed.
248
249 @param c The continuation to execute.
250 */
251 BOOST_CAPY_DECL
252 void
253 post(continuation& c) const;
254
255 /** Return true if two executors refer to the same thread pool.
256
257 @param other The executor to compare against.
258
259 @return `true` if both executors refer to the same pool.
260 */
261 bool
262 13x operator==(executor_type const& other) const noexcept
263 {
264 13x return pool_ == other.pool_;
265 }
266 };
267
268 } // capy
269 } // boost
270
271 #endif
272