10#include "../callable/bind.hpp"
11#include "../callable/move_only_function.hpp"
22#include <condition_variable>
32#include <system_error>
96template <
typename WaitPolicy>
114 : num_threads_(
checked_worker_count(num_threads)), register_workers_(register_workers), stop_(false),
115 start_time_(
std::chrono::steady_clock::now())
117 workers_.reserve(num_threads_);
121 for (
size_t i = 0; i < num_threads_; ++i)
122 workers_.emplace_back(&thread_pool_backend_base::worker_function,
this, i);
123 startup_.
wait(num_threads_);
127 stop_.store(
true, std::memory_order_release);
128 condition_.notify_all();
129 for (
auto& worker : workers_)
130 if (worker.joinable())
152 template <
typename F,
typename... Args>
158 auto task = std::make_shared<std::packaged_task<return_type()>>(
161 std::future<return_type>
result = task->get_future();
164 std::lock_guard<std::mutex> lock(queue_mutex_);
166 return unexpected(std::make_error_code(std::errc::operation_canceled));
167 tasks_.emplace([task]() { (*task)(); });
170 condition_.notify_one();
178 template <
typename F,
typename... Args>
184 throw std::runtime_error(
"Pool is shutting down");
196 template <
typename F,
typename... Args>
200 auto r =
try_post(std::forward<F>(f), std::forward<Args>(args)...);
202 throw std::runtime_error(
"Pool is shutting down");
210 template <
typename F,
typename... Args>
216 std::lock_guard<std::mutex> lock(queue_mutex_);
218 return unexpected(std::make_error_code(std::errc::operation_canceled));
219 tasks_.push(std::move(task));
221 condition_.notify_one();
231 template <
typename Iterator>
235 std::vector<std::future<void>> futures;
237 futures.reserve(batch_size_hint);
238 std::vector<std::shared_ptr<std::packaged_task<void()>>> prepared;
239 prepared.reserve(batch_size_hint);
241 for (
auto it = begin; it != end; ++it)
243 auto task = std::make_shared<std::packaged_task<void()>>(*it);
244 futures.push_back(task->get_future());
245 prepared.push_back(std::move(task));
248 bool enqueued =
false;
251 std::lock_guard<std::mutex> lock(queue_mutex_);
253 return unexpected(std::make_error_code(std::errc::operation_canceled));
255 for (
auto const& task : prepared)
257 tasks_.emplace([task]() { (*task)(); });
264 condition_.notify_all();
268 condition_.notify_all();
273 template <
typename Iterator>
275 submit_batch(Iterator begin, Iterator end) -> std::vector<std::future<void>>
279 throw std::runtime_error(
"Pool is shutting down");
285 template <
typename Iterator,
typename F>
301 size() const noexcept ->
size_t
310 std::lock_guard<std::mutex> lock(queue_mutex_);
311 return tasks_.size();
363 std::unique_lock<std::mutex> lock(queue_mutex_);
364 task_finished_condition_.wait(lock, [
this]
365 {
return tasks_.empty() && active_tasks_.load(std::memory_order_acquire) == 0; });
371 return current_pool ==
this;
384 std::lock_guard<std::recursive_timed_mutex> shutdown_lock(shutdown_mutex_);
385 if (workers_.empty())
387 std::queue<queued_task> discarded;
389 std::lock_guard<std::mutex> lock(queue_mutex_);
393 tasks_.swap(discarded);
395 shutdown_completed_all_ = shutdown_completed_all_ && discarded.empty();
397 condition_.notify_all();
398 task_finished_condition_.notify_all();
400 std::queue<queued_task> empty;
401 discarded.swap(empty);
404 for (
auto& worker : workers_)
406 if (worker.joinable())
411 shutdown_completed_at_ = std::chrono::steady_clock::now();
430 std::unique_lock<std::recursive_timed_mutex> shutdown_lock(shutdown_mutex_, std::defer_lock);
431 if (deadline == std::chrono::steady_clock::time_point::max())
432 shutdown_lock.lock();
433 else if (!shutdown_lock.try_lock_until(deadline))
436 std::unique_lock<std::mutex> lock(queue_mutex_);
437 if (workers_.empty())
438 return shutdown_completed_all_ && shutdown_completed_at_ <= deadline;
441 condition_.notify_all();
442 bool const drained = task_finished_condition_.wait_until(
443 lock, deadline, [
this] {
return tasks_.empty() && active_tasks_.load(std::memory_order_acquire) == 0; });
444 std::queue<queued_task> discarded;
446 tasks_.swap(discarded);
447 shutdown_completed_all_ = shutdown_completed_all_ && discarded.empty();
450 condition_.notify_all();
451 task_finished_condition_.notify_all();
453 std::queue<queued_task> empty;
454 discarded.swap(empty);
456 if (deadline == std::chrono::steady_clock::time_point::max())
458 for (
auto& worker : workers_)
459 if (worker.joinable())
463 if (drained && shutdown_completed_at_ == std::chrono::steady_clock::time_point{})
464 shutdown_completed_at_ = std::chrono::steady_clock::now();
465 return drained && shutdown_completed_all_;
477 auto const now = std::chrono::steady_clock::now();
478 auto const elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - start_time_);
480 std::lock_guard<std::mutex> lock(queue_mutex_);
483 stats.
active_threads = active_tasks_.load(std::memory_order_acquire);
485 stats.
completed_tasks = completed_tasks_.load(std::memory_order_acquire);
487 if (elapsed.count() > 0)
496 auto const total_task_time = total_task_time_.load(std::memory_order_acquire);
521 std::lock_guard<std::mutex> lock(trace_mutex_);
525 template <
typename Callback,
526 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>,
task_start_callback>,
int> = 0>
530 static_assert(std::is_invocable_r_v<void, Callback&, std::chrono::steady_clock::time_point, std::thread::id>,
531 "Task start callback must accept (time_point, std::thread::id)");
532 std::lock_guard<std::mutex> lock(trace_mutex_);
533 on_task_start_ = detail::make_copyable_function<void(std::chrono::steady_clock::time_point, std::thread::id)>(
534 std::forward<Callback>(cb));
545 std::lock_guard<std::mutex> lock(trace_mutex_);
549 template <
typename Callback,
550 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>,
task_end_callback>,
int> = 0>
554 static_assert(std::is_invocable_r_v<void, Callback&, std::chrono::steady_clock::time_point, std::thread::id,
555 std::chrono::microseconds>,
556 "Task end callback must accept (time_point, std::thread::id, "
557 "std::chrono::microseconds)");
558 std::lock_guard<std::mutex> lock(trace_mutex_);
560 std::chrono::microseconds)>(std::forward<Callback>(cb));
567 bool register_workers_;
569 std::vector<detail::thread_backend> workers_;
570 std::queue<queued_task> tasks_;
572 mutable std::mutex queue_mutex_;
573 std::condition_variable condition_;
574 std::condition_variable task_finished_condition_;
575 std::recursive_timed_mutex shutdown_mutex_;
576 std::atomic<bool> stop_;
577 bool shutdown_completed_all_{
true };
578 std::chrono::steady_clock::time_point shutdown_completed_at_{};
579 std::atomic<size_t> active_tasks_{ 0 };
580 std::atomic<size_t> completed_tasks_{ 0 };
581 std::atomic<uint64_t> total_task_time_{ 0 };
583 std::mutex trace_mutex_;
587 std::chrono::steady_clock::time_point start_time_;
591 worker_function(
size_t worker_id)
593 detail::worker_context_guard<thread_pool_backend_base> worker_context(current_pool,
this);
594 std::optional<registration_guard_backend> reg_guard;
597 if (register_workers_)
598 reg_guard.emplace(
"pool_worker_" + std::to_string(worker_id),
"threadschedule.pool");
603 startup_.
arrive(std::current_exception());
610 bool found_task =
false;
613 std::unique_lock<std::mutex> lock(queue_mutex_);
615 if (WaitPolicy::wait(condition_, lock, [
this] {
return stop_ || !tasks_.empty(); }))
617 if (stop_ && tasks_.empty())
624 task = std::move(tasks_.front());
627 active_tasks_.fetch_add(1, std::memory_order_relaxed);
638 auto const start_time = std::chrono::steady_clock::now();
639 auto const tid = std::this_thread::get_id();
645 std::lock_guard<std::mutex> tl(trace_mutex_);
646 on_task_start = on_task_start_;
649 on_task_start(start_time, tid);
663 auto const end_time = std::chrono::steady_clock::now();
665 auto const task_duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
666 total_task_time_.fetch_add(task_duration.count(), std::memory_order_relaxed);
672 std::lock_guard<std::mutex> tl(trace_mutex_);
673 on_task_end = on_task_end_;
676 on_task_end(end_time, tid, task_duration);
683 std::lock_guard<std::mutex> lock(queue_mutex_);
684 active_tasks_.fetch_sub(1, std::memory_order_relaxed);
686 completed_tasks_.fetch_add(1, std::memory_order_relaxed);
688 task_finished_condition_.notify_all();
Manages a set of CPU indices to which a thread may be bound.
Value-semantic wrapper for a thread scheduling priority.
static constexpr auto normal() noexcept -> native_thread_priority
Single-queue thread pool parameterized by its idle-wait strategy.
auto is_current_worker() const noexcept -> bool
void post(F &&f, Args &&... args)
Fire-and-forget task submission (throwing variant).
auto size() const noexcept -> size_t
Number of worker threads.
auto submit_batch(Iterator begin, Iterator end) -> std::vector< std::future< void > >
Submit a batch of tasks (throwing).
auto submit(F &&f, Args &&... args) -> std::future< bind_result_t< F, Args... > >
Submit a task, throwing on shutdown.
auto try_post(F &&f, Args &&... args) -> expected< void, std::error_code >
Fire-and-forget task submission (non-throwing variant).
thread_pool_backend_base(size_t num_threads=default_worker_count(), bool register_workers=false)
auto set_affinity(native_thread_affinity const &affinity) -> expected< void, std::error_code >
Pin all workers to the same CPU set.
auto operator=(thread_pool_backend_base const &) -> thread_pool_backend_base &=delete
void set_on_task_start(task_start_callback cb)
Register a callback invoked just before each task executes.
void parallel_for_each(Iterator begin, Iterator end, F &&func)
Apply func to [begin, end) in parallel (chunked).
auto get_statistics() const -> statistics
Collect approximate performance counters.
detail::move_only_function< void()> queued_task
~thread_pool_backend_base()
thread_pool_backend_base(thread_pool_backend_base const &)=delete
auto configure_threads(std::string const &name_prefix, native_scheduling_policy policy=native_scheduling_policy::other, native_thread_priority priority=native_thread_priority::normal()) -> expected< void, std::error_code >
Name, schedule and prioritize all worker threads.
auto shutdown_for(std::chrono::milliseconds timeout) -> bool
Attempt a timed drain: finish as many tasks as possible within timeout, then discard queued work.
auto try_submit_batch(Iterator begin, Iterator end) -> expected< std::vector< std::future< void > >, std::error_code >
Submit a range of void() callables in one go (non-throwing).
void wait_for_tasks()
Block until all pending and active tasks have completed.
void shutdown(shutdown_policy_backend policy=shutdown_policy_backend::drain)
Shut the pool down.
void set_on_task_start(Callback &&cb)
std::function< void()> task_type
auto pending_tasks() const -> size_t
Number of tasks waiting in the queue.
void set_on_task_end(task_end_callback cb)
Register a callback invoked just after each task completes.
auto try_submit(F &&f, Args &&... args) -> expected< std::future< bind_result_t< F, Args... > >, std::error_code >
Submit a task without throwing on shutdown.
auto configure_threads(native_thread_config const &config) -> expected< void, std::error_code >
void set_on_task_end(Callback &&cb)
auto distribute_across_cpus() -> expected< void, std::error_code >
Pin each worker to a distinct CPU core (round-robin).
void arrive(std::exception_ptr error={})
void wait(size_t expected)
constexpr auto has_value() const noexcept -> bool
Blocking idle-wait strategy for queue-based pools.
Aggregates multiple thread_registry_backend instances into a single queryable view.
auto configure_worker_threads(WorkerRange &workers, std::string const &name_prefix, native_scheduling_policy policy, native_thread_priority priority, thread_registry_backend *registry=nullptr) -> expected< void, std::error_code >
native_scheduling_policy
Enumeration of available thread scheduling policies.
@ other
Standard round-robin time-sharing.
auto distribute_workers_across_cpus(WorkerRange &workers) -> expected< void, std::error_code >
task_start_callback task_start_callback_storage
auto multipass_range_size(Iterator begin, Iterator end) -> size_t
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.
task_end_callback task_end_callback_storage
copyable_function< void(std::chrono::steady_clock::time_point, std::thread::id)> task_start_callback
auto default_worker_count() noexcept -> std::size_t
auto bind_args(F &&function, Args &&... args)
auto checked_worker_count(std::size_t count) -> std::size_t
void throw_worker_deadlock()
auto make_copyable_function(Callable &&callable) -> copyable_function< Signature >
auto set_worker_affinity(WorkerRange &workers, native_thread_affinity const &affinity) -> expected< void, std::error_code >
copyable_function< void(std::chrono::steady_clock::time_point, std::thread::id, std::chrono::microseconds elapsed)> task_end_callback
auto runtime_registry() -> thread_registry_backend &
void parallel_for_each_chunked(Pool &pool, Iterator begin, Iterator end, F &&func, size_t num_workers)
auto shutdown_deadline_after(std::chrono::milliseconds timeout) -> std::chrono::steady_clock::time_point
expected< T, std::error_code > result
Standard result type used by public APIs.
Timed idle-wait strategy for queue-based pools.
Internal queued-task shutdown behavior.
std::chrono::microseconds avg_task_time
Worker identity, CPU selection, and registration helpers.
Worker-thread count configuration for pool types.