ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
thread_pool.hpp
Go to the documentation of this file.
1#pragma once
2
11#include "shutdown_policy.hpp"
12#include "task_error.hpp"
13#include "thread_config.hpp"
14#include "worker_count.hpp"
16
17#include <cstddef>
18#include <future>
19#include <memory>
20#include <system_error>
21#include <thread>
22#include <type_traits>
23#include <utility>
24
25namespace threadschedule
26{
31{
32public:
34 auto
36 {
37 worker_count_ = value;
38 return *this;
39 }
41 auto
43 {
44 registration_ = value;
45 return *this;
46 }
48 auto
50 {
51 workers_ = std::move(value);
52 return *this;
53 }
55 auto
57 {
58 shutdown_ = value;
59 return *this;
60 }
65 auto
67 {
68 on_task_error_ = std::move(value);
69 return *this;
70 }
71
73 [[nodiscard]] auto
74 get_worker_count() const noexcept -> worker_count
75 {
76 return worker_count_;
77 }
79 [[nodiscard]] auto
81 {
82 return registration_;
83 }
85 [[nodiscard]] auto
86 get_worker_config() const noexcept -> thread_config const&
87 {
88 return workers_;
89 }
91 [[nodiscard]] auto
93 {
94 return shutdown_;
95 }
97 [[nodiscard]] auto
98 get_error_callback() const noexcept -> error_callback const&
99 {
100 return on_task_error_;
101 }
102
103private:
104 worker_count worker_count_{ worker_count::automatic() };
106 thread_config workers_;
108 error_callback on_task_error_;
109};
110
115{
116public:
119
124 explicit thread_pool(worker_count count) : thread_pool(thread_pool_config{}.set_worker_count(count)) {}
125
132 : config_(std::move(config)),
133 impl_(std::make_unique<detail::thread_pool_backend>(
134 config_.get_worker_count().resolve(), config_.get_registration() == worker_registration::global_registry))
135 {
137 {
138 auto configured = impl_->configure_threads(detail::to_native(config_.get_worker_config()));
139 if (!configured)
140 throw std::system_error(configured.error(), "thread_pool worker configuration");
141 }
142 }
143
144 thread_pool(thread_pool&&) noexcept = default;
145 auto
146 operator=(thread_pool&& other) noexcept -> thread_pool&
147 {
148 if (this != &other)
149 {
150 dispose_impl();
151 config_ = std::move(other.config_);
152 impl_ = std::move(other.impl_);
153 }
154 return *this;
155 }
156 thread_pool(thread_pool const&) = delete;
157 auto operator=(thread_pool const&) -> thread_pool& = delete;
158
164 static auto
166 {
167 return detail::try_result([&config]() -> result<thread_pool> { return thread_pool(std::move(config)); });
168 }
169
176 {
177 dispose_impl();
178 }
179
186 template <typename F, typename... Args>
187 auto
188 submit(F&& function, Args&&... args) -> result<std::future<detail::bind_result_t<F, Args...>>>
189 {
190 if (!impl_)
191 return unexpected(std::make_error_code(std::errc::operation_canceled));
192 return detail::try_result(
193 [&]() -> result<std::future<detail::bind_result_t<F, Args...>>>
194 {
195 using return_type = detail::bind_result_t<F, Args...>;
196 if (!config_.get_error_callback())
197 return impl_->try_submit(std::forward<F>(function), std::forward<Args>(args)...);
198
199 auto callback = config_.get_error_callback();
200 auto wrapped = [bound = detail::bind_args(std::forward<F>(function), std::forward<Args>(args)...),
201 callback = std::move(callback)]() mutable -> return_type
202 {
203 try
204 {
205 if constexpr (std::is_void_v<return_type>)
206 {
207 bound();
208 return;
209 }
210 else
211 {
212 return bound();
213 }
214 }
215 catch (...)
216 {
217 auto original = std::current_exception();
218 try
219 {
220 callback(task_error::capture());
221 }
222 catch (...)
223 {
224 }
225 std::rethrow_exception(original);
226 }
227 };
228 return impl_->try_submit(std::move(wrapped));
229 });
230 }
231
236 template <typename F, typename... Args>
237 auto
238 submit_or_throw(F&& function, Args&&... args) -> std::future<detail::bind_result_t<F, Args...>>
239 {
240 auto submitted = submit(std::forward<F>(function), std::forward<Args>(args)...);
241 if (!submitted)
242 throw std::system_error(submitted.error(), "thread_pool::submit");
243 return std::move(*submitted);
244 }
245
252 template <typename F, typename... Args>
253 auto
254 post(F&& function, Args&&... args) -> result<void>
255 {
256 if (!impl_)
257 return unexpected(std::make_error_code(std::errc::operation_canceled));
258 return detail::try_result(
259 [&]() -> result<void>
260 {
261 if (!config_.get_error_callback())
262 return impl_->try_post(std::forward<F>(function), std::forward<Args>(args)...);
263
264 auto callback = config_.get_error_callback();
265 auto wrapped = [bound = detail::bind_args(std::forward<F>(function), std::forward<Args>(args)...),
266 callback = std::move(callback)]() mutable
267 {
268 try
269 {
270 bound();
271 }
272 catch (...)
273 {
274 try
275 {
276 callback(task_error::capture());
277 }
278 catch (...)
279 {
280 }
281 }
282 };
283 return impl_->try_post(std::move(wrapped));
284 });
285 }
286
291 template <typename F, typename... Args>
292 void
293 post_or_throw(F&& function, Args&&... args)
294 {
295 auto posted = post(std::forward<F>(function), std::forward<Args>(args)...);
296 if (!posted)
297 throw std::system_error(posted.error(), "thread_pool::post");
298 }
299
303 auto
305 {
306 if (!impl_)
307 return unexpected(std::make_error_code(std::errc::operation_canceled));
308 return detail::try_result(
309 [&]() -> result<void>
310 {
311 impl_->wait_for_tasks();
312 return {};
313 });
314 }
315
319 void
321 {
322 if (!impl_)
323 throw std::system_error(std::make_error_code(std::errc::operation_canceled), "thread_pool::wait");
324 impl_->wait_for_tasks();
325 }
326
331 auto
333 {
334 if (!impl_)
335 return unexpected(std::make_error_code(std::errc::operation_canceled));
336 return detail::try_result([&]() -> result<void> { return impl_->configure_threads(detail::to_native(config)); });
337 }
338
342 auto
344 {
345 return shutdown(config_.get_shutdown_policy());
346 }
347
352 auto
354 {
355 if (!impl_)
356 return {};
357 return detail::try_result(
358 [&]() -> result<void>
359 {
360 impl_->shutdown(detail::to_native(policy));
361 return {};
362 });
363 }
364
366 [[nodiscard]] auto
367 size() const noexcept -> std::size_t
368 {
369 return impl_ ? impl_->size() : 0;
370 }
371
372private:
373 void
374 dispose_impl() noexcept
375 {
376 if (!impl_)
377 return;
378
379 auto const policy = detail::to_native(config_.get_shutdown_policy());
380 if (!impl_->is_current_worker())
381 {
382 try
383 {
384 impl_->shutdown(policy);
385 }
386 catch (...)
387 {
388 }
389 impl_.reset();
390 return;
391 }
392
393 // The backend remains in use until the current task returns. Transfer its
394 // ownership to a reaper thread, which can safely join every worker.
395 auto* backend = impl_.release();
396 try
397 {
398 std::thread reaper(
399 [backend, policy]() noexcept
400 {
401 std::unique_ptr<detail::thread_pool_backend> owner(backend);
402 try
403 {
404 owner->shutdown(policy);
405 }
406 catch (...)
407 {
408 }
409 });
410 reaper.detach();
411 }
412 catch (...)
413 {
414 // No thread can safely reclaim a backend that is executing this
415 // destructor. Keeping it alive is the only non-terminating fallback.
416 (void)backend;
417 }
418 }
419
420 thread_pool_config config_;
421 std::unique_ptr<detail::thread_pool_backend> impl_;
422};
423
424} // namespace threadschedule
Portable thread configuration bundle.
Builder-style configuration for thread_pool.
auto set_registration(worker_registration value) noexcept -> thread_pool_config &
Configure registry registration behavior for workers.
auto get_registration() const noexcept -> worker_registration
Return configured worker registration mode.
auto get_worker_count() const noexcept -> worker_count
Return configured worker count.
auto set_shutdown_policy(shutdown_policy value) noexcept -> thread_pool_config &
Set shutdown behavior used by thread_pool::shutdown and destructor.
auto get_shutdown_policy() const noexcept -> shutdown_policy
Return configured shutdown policy.
auto get_error_callback() const noexcept -> error_callback const &
Return configured asynchronous task error callback.
auto set_worker_count(worker_count value) noexcept -> thread_pool_config &
Set number of worker threads.
auto set_worker_config(thread_config value) -> thread_pool_config &
Set startup configuration applied to worker threads.
auto set_error_callback(error_callback value) -> thread_pool_config &
Set callback invoked when posted/submitted work throws.
auto get_worker_config() const noexcept -> thread_config const &
Return worker thread configuration.
Fixed-size thread pool for queued asynchronous work.
auto wait() -> result< void >
Wait until all queued/running tasks are finished.
thread_pool(worker_count count)
Construct a pool with explicit worker count.
auto post(F &&function, Args &&... args) -> result< void >
Post fire-and-forget work to the pool.
auto size() const noexcept -> std::size_t
Return configured worker count (0 if moved-from; retained after shutdown).
auto submit(F &&function, Args &&... args) -> result< std::future< detail::bind_result_t< F, Args... > > >
Submit work and receive a future for its result.
~thread_pool()
Shutdown the pool according to configured policy.
auto shutdown(shutdown_policy policy) -> result< void >
Shutdown with explicit policy.
static auto create(thread_pool_config config={}) -> result< thread_pool >
Create a pool without throwing.
auto configure_workers(thread_config const &config) -> result< void >
Apply thread configuration to all workers.
thread_pool()
Construct a pool with default configuration.
thread_pool(thread_pool &&) noexcept=default
void wait_or_throw()
Throwing counterpart to wait.
auto operator=(thread_pool const &) -> thread_pool &=delete
thread_pool(thread_pool_config config)
Construct a pool from full configuration.
auto submit_or_throw(F &&function, Args &&... args) -> std::future< detail::bind_result_t< F, Args... > >
Throwing counterpart to submit.
void post_or_throw(F &&function, Args &&... args)
Throwing counterpart to post.
thread_pool(thread_pool const &)=delete
auto shutdown() -> result< void >
Shutdown using configured policy.
Value type for pool worker count.
static constexpr auto automatic() noexcept -> worker_count
Create automatic worker count.
std::invoke_result_t< decltype(bind_args(std::declval< F >(), std::declval< Args >()...))& > bind_result_t
Result of invoking the decayed callable and arguments stored by bind_args.
Definition bind.hpp:32
constexpr auto to_native(shutdown_policy policy) noexcept -> shutdown_policy_backend
Definition shutdown.hpp:10
auto bind_args(F &&function, Args &&... args)
Definition bind.hpp:17
auto has_thread_configuration(thread_config const &config) noexcept -> bool
Definition control.hpp:123
auto try_result(Function &&function) -> decltype(std::forward< Function >(function)())
auto global_registry() -> thread_registry &
shutdown_policy
Defines how pools handle pending work during shutdown.
@ drain
Finish queued work before returning from shutdown.
std::function< void(task_error const &)> error_callback
Callback signature for asynchronous task error reporting.
worker_registration
Controls whether pool workers are registered in a thread registry.
@ disabled
Do not register workers in the global registry.
Portable pool shutdown behavior.
static auto capture(std::string description={}) -> task_error
Capture failure context for the current exception state.
Portable task failure reporting.
Portable thread startup and runtime configuration.
Worker-thread count configuration for pool types.
Worker registration behavior for pool-managed threads.