ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
work_stealing_pool_backend.hpp
Go to the documentation of this file.
1#pragma once
2
10#include "../callable/bind.hpp"
11#include "../callable/move_only_function.hpp"
12#include "callbacks.hpp"
13#include "deadline.hpp"
17#include "worker_count.hpp"
18
19#include <atomic>
20#include <chrono>
21#include <condition_variable>
22#include <cstddef>
23#include <cstdint>
24#include <functional>
25#include <future>
26#include <memory>
27#include <mutex>
28#include <optional>
29#include <queue>
30#include <random>
31#include <shared_mutex>
32#include <string>
33#include <system_error>
34#include <type_traits>
35#include <utility>
36#include <vector>
37
39{
40
123{
124public:
125 using task_type = std::function<void()>;
127
129 {
136 std::chrono::microseconds avg_task_time;
137 };
138
139 explicit work_stealing_pool_backend(size_t num_threads = default_worker_count(),
141 bool register_workers = false)
142 : num_threads_(checked_worker_count(num_threads)), register_workers_(register_workers), stop_(false),
143 next_victim_(0), start_time_(std::chrono::steady_clock::now())
144 {
145 worker_queues_.resize(num_threads_);
146 for (size_t i = 0; i < num_threads_; ++i)
147 {
148 worker_queues_[i] = std::make_unique<work_stealing_deque<queued_task>>(deque_capacity);
149 }
150
151 workers_.reserve(num_threads_);
152
153 try
154 {
155 for (size_t i = 0; i < num_threads_; ++i)
156 workers_.emplace_back(&work_stealing_pool_backend::worker_function, this, i);
157 startup_.wait(num_threads_);
158 }
159 catch (...)
160 {
161 stop_.store(true, std::memory_order_release);
162 submissions_quiesced_.store(true, std::memory_order_release);
163 wakeup_condition_.notify_all();
164 for (auto& worker : workers_)
165 if (worker.joinable())
166 worker.join();
167 throw;
168 }
169 }
170
171 template <typename Bool, std::enable_if_t<std::is_same_v<std::decay_t<Bool>, bool>, int> = 0>
172 work_stealing_pool_backend(size_t num_threads, Bool register_workers)
173 : work_stealing_pool_backend(num_threads, work_stealing_deque<queued_task>::default_capacity, register_workers)
174 {
175 }
176
179
184
191 void
193 {
194 if (is_current_worker())
196 std::lock_guard<std::recursive_timed_mutex> shutdown_lock(shutdown_mutex_);
197 if (workers_.empty())
198 return;
199 stop_.store(true, std::memory_order_release);
200 bool const completed_all = finish_shutdown(policy);
201 shutdown_completed_all_ = shutdown_completed_all_ && completed_all;
202 shutdown_completed_at_ = std::chrono::steady_clock::now();
203 }
204
215 auto
216 shutdown_for(std::chrono::milliseconds timeout) -> bool
217 {
218 if (is_current_worker())
220 auto const deadline = shutdown_deadline_after(timeout);
221 std::unique_lock<std::recursive_timed_mutex> shutdown_lock(shutdown_mutex_, std::defer_lock);
222 if (deadline == std::chrono::steady_clock::time_point::max())
223 shutdown_lock.lock();
224 else if (!shutdown_lock.try_lock_until(deadline))
225 return false;
226
227 if (workers_.empty())
228 return shutdown_completed_all_ && shutdown_completed_at_ <= deadline;
229
230 stop_.store(true, std::memory_order_release);
231 if (!submissions_quiesced_.load(std::memory_order_acquire))
232 {
233 std::unique_lock<std::shared_timed_mutex> submission_lock(submission_mutex_, std::defer_lock);
234 if (deadline == std::chrono::steady_clock::time_point::max())
235 submission_lock.lock();
236 else if (!submission_lock.try_lock_until(deadline))
237 return false;
238 submissions_quiesced_.store(true, std::memory_order_release);
239 }
240 wakeup_condition_.notify_all();
241
242 std::unique_lock<std::mutex> lock(completion_mutex_);
243 bool const drained = completion_condition_.wait_until(
244 lock, deadline, [this] { return outstanding_tasks_.load(std::memory_order_acquire) == 0; });
245 lock.unlock();
246
247 if (!drained)
248 {
249 size_t const dropped_tasks = discard_pending_tasks();
250 shutdown_completed_all_ = shutdown_completed_all_ && dropped_tasks == 0;
251 }
252
253 wakeup_condition_.notify_all();
254 completion_condition_.notify_all();
255
256 if (deadline == std::chrono::steady_clock::time_point::max())
257 {
258 for (auto& worker : workers_)
259 if (worker.joinable())
260 worker.join();
261 workers_.clear();
262 }
263 if (drained && shutdown_completed_at_ == std::chrono::steady_clock::time_point{})
264 shutdown_completed_at_ = std::chrono::steady_clock::now();
265 return drained && shutdown_completed_all_;
266 }
267
284 template <typename F, typename... Args>
285 auto
286 try_submit(F&& f, Args&&... args) -> expected<std::future<bind_result_t<F, Args...>>, std::error_code>
287 {
288 using return_type = bind_result_t<F, Args...>;
289
290 auto task = std::make_shared<std::packaged_task<return_type()>>(
291 detail::bind_args(std::forward<F>(f), std::forward<Args>(args)...));
292
293 std::future<return_type> result = task->get_future();
294 queued_task queued([task]() { (*task)(); });
295
296 std::shared_lock<std::shared_timed_mutex> submission_lock(submission_mutex_);
297
298 if (stop_.load(std::memory_order_acquire))
299 return unexpected(std::make_error_code(std::errc::operation_canceled));
300
301 size_t const preferred_queue = next_victim_.fetch_add(1, std::memory_order_relaxed) % num_threads_;
302
303 outstanding_tasks_.fetch_add(1, std::memory_order_release);
304 if (worker_queues_[preferred_queue]->push(std::move(queued)))
305 {
306 wakeup_condition_.notify_one();
307 return result;
308 }
309 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
310
311 for (size_t attempts = 0; attempts < (std::min)(num_threads_, size_t(3)); ++attempts)
312 {
313 size_t const idx = (preferred_queue + attempts + 1) % num_threads_;
314 outstanding_tasks_.fetch_add(1, std::memory_order_release);
315 // A failed push does not consume the queued task.
316 // NOLINTNEXTLINE(bugprone-use-after-move)
317 if (worker_queues_[idx]->push(std::move(queued)))
318 {
319 wakeup_condition_.notify_one();
320 return result;
321 }
322 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
323 }
324
325 {
326 std::lock_guard<std::mutex> lock(overflow_mutex_);
327 if (stop_.load(std::memory_order_relaxed))
328 return unexpected(std::make_error_code(std::errc::operation_canceled));
329 // Failed deque pushes preserve the queued task.
330 // NOLINTNEXTLINE(bugprone-use-after-move)
331 overflow_tasks_.emplace(std::move(queued));
332 outstanding_tasks_.fetch_add(1, std::memory_order_release);
333 }
334
335 wakeup_condition_.notify_all();
336 return result;
337 }
338
348 template <typename F, typename... Args>
349 auto
350 submit(F&& f, Args&&... args) -> std::future<bind_result_t<F, Args...>>
351 {
352 auto result = try_submit(std::forward<F>(f), std::forward<Args>(args)...);
353 if (!result.has_value())
354 throw std::runtime_error("work_stealing_pool_backend is shutting down");
355 return std::move(result.value());
356 }
357
368 template <typename F, typename... Args>
369 void
370 post(F&& f, Args&&... args)
371 {
372 auto r = try_post(std::forward<F>(f), std::forward<Args>(args)...);
373 if (!r.has_value())
374 throw std::runtime_error("work_stealing_pool_backend is shutting down");
375 }
376
383 template <typename F, typename... Args>
384 auto
386 {
387 queued_task bound(
388 detail::make_move_only_function<void()>(detail::bind_args(std::forward<F>(f), std::forward<Args>(args)...)));
389
390 std::shared_lock<std::shared_timed_mutex> submission_lock(submission_mutex_);
391
392 if (stop_.load(std::memory_order_acquire))
393 return unexpected(std::make_error_code(std::errc::operation_canceled));
394
395 size_t const preferred_queue = next_victim_.fetch_add(1, std::memory_order_relaxed) % num_threads_;
396
397 outstanding_tasks_.fetch_add(1, std::memory_order_release);
398 if (worker_queues_[preferred_queue]->push(std::move(bound)))
399 {
400 wakeup_condition_.notify_one();
401 return {};
402 }
403 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
404
405 for (size_t attempts = 0; attempts < (std::min)(num_threads_, size_t(3)); ++attempts)
406 {
407 size_t const idx = (preferred_queue + attempts + 1) % num_threads_;
408 outstanding_tasks_.fetch_add(1, std::memory_order_release);
409 // work_stealing_deque::push only moves from the task after it has
410 // confirmed capacity; a failed push deliberately preserves it.
411 // NOLINTNEXTLINE(bugprone-use-after-move)
412 if (worker_queues_[idx]->push(std::move(bound)))
413 {
414 wakeup_condition_.notify_one();
415 return {};
416 }
417 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
418 }
419
420 {
421 std::lock_guard<std::mutex> lock(overflow_mutex_);
422 if (stop_.load(std::memory_order_relaxed))
423 return unexpected(std::make_error_code(std::errc::operation_canceled));
424 // Failed deque pushes preserve the queued task.
425 // NOLINTNEXTLINE(bugprone-use-after-move)
426 overflow_tasks_.emplace(std::move(bound));
427 outstanding_tasks_.fetch_add(1, std::memory_order_release);
428 }
429
430 wakeup_condition_.notify_all();
431 return {};
432 }
433
446 template <typename Iterator>
447 auto
448 try_submit_batch(Iterator begin, Iterator end) -> expected<std::vector<std::future<void>>, std::error_code>
449 {
450 std::vector<std::future<void>> futures;
451 size_t const batch_size_hint = detail::multipass_range_size(begin, end);
452 futures.reserve(batch_size_hint);
453 std::vector<queued_task> prepared;
454 prepared.reserve(batch_size_hint);
455
456 for (auto it = begin; it != end; ++it)
457 {
458 auto task = std::make_shared<std::packaged_task<void()>>(*it);
459 futures.push_back(task->get_future());
460 prepared.emplace_back([task]() { (*task)(); });
461 }
462
463 size_t const batch_size = prepared.size();
464
465 std::shared_lock<std::shared_timed_mutex> submission_lock(submission_mutex_);
466
467 if (stop_.load(std::memory_order_acquire))
468 return unexpected(std::make_error_code(std::errc::operation_canceled));
469
470 size_t queue_idx = next_victim_.fetch_add(batch_size, std::memory_order_relaxed) % num_threads_;
471
472 try
473 {
474 for (auto& queued : prepared)
475 {
476 bool enqueued = false;
477 for (size_t attempts = 0; attempts < num_threads_; ++attempts)
478 {
479 outstanding_tasks_.fetch_add(1, std::memory_order_release);
480 // A failed push does not consume the queued task.
481 // NOLINTNEXTLINE(bugprone-use-after-move)
482 if (worker_queues_[queue_idx]->push(std::move(queued)))
483 {
484 enqueued = true;
485 queue_idx = (queue_idx + 1) % num_threads_;
486 break;
487 }
488 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
489 queue_idx = (queue_idx + 1) % num_threads_;
490 }
491
492 if (!enqueued)
493 {
494 std::lock_guard<std::mutex> lock(overflow_mutex_);
495 // Failed deque pushes preserve the queued task.
496 // NOLINTNEXTLINE(bugprone-use-after-move)
497 overflow_tasks_.emplace(std::move(queued));
498 outstanding_tasks_.fetch_add(1, std::memory_order_release);
499 }
500 }
501 }
502 catch (...)
503 {
504 wakeup_condition_.notify_all();
505 throw;
506 }
507
508 wakeup_condition_.notify_all();
509 return futures;
510 }
511
517 template <typename Iterator>
518 auto
519 submit_batch(Iterator begin, Iterator end) -> std::vector<std::future<void>>
520 {
521 auto result = try_submit_batch(begin, end);
522 if (!result.has_value())
523 throw std::runtime_error("work_stealing_pool_backend is shutting down");
524 return std::move(result.value());
525 }
526
534 template <typename Iterator, typename F>
535 void
536 parallel_for_each(Iterator begin, Iterator end, F&& func)
537 {
538 if (is_current_worker())
540 detail::parallel_for_each_chunked(*this, begin, end, std::forward<F>(func), num_threads_);
541 }
542
545
547 [[nodiscard]] auto
548 size() const noexcept -> size_t
549 {
550 return num_threads_;
551 }
552
554 [[nodiscard]] auto
555 pending_tasks() const -> size_t
556 {
557 size_t total = 0;
558 for (auto const& queue : worker_queues_)
559 {
560 total += queue->size();
561 }
562
563 std::lock_guard<std::mutex> lock(overflow_mutex_);
564 total += overflow_tasks_.size();
565 return total;
566 }
567
569 auto
571 {
572 auto const now = std::chrono::steady_clock::now();
573 auto const elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - start_time_);
574
575 statistics stats;
576 stats.total_threads = num_threads_;
577 stats.active_threads = active_tasks_.load(std::memory_order_acquire);
579 stats.completed_tasks = completed_tasks_.load(std::memory_order_acquire);
580 stats.stolen_tasks = stolen_tasks_.load(std::memory_order_acquire);
581
582 if (elapsed.count() > 0)
583 {
584 stats.tasks_per_second = static_cast<double>(stats.completed_tasks) / elapsed.count();
585 }
586 else
587 {
588 stats.tasks_per_second = 0.0;
589 }
590
591 auto const total_task_time = total_task_time_.load(std::memory_order_acquire);
592 if (stats.completed_tasks > 0)
593 {
594 stats.avg_task_time = std::chrono::microseconds(total_task_time / stats.completed_tasks);
595 }
596 else
597 {
598 stats.avg_task_time = std::chrono::microseconds(0);
599 }
600
601 return stats;
602 }
603
605
608
617 auto
621 {
622 return detail::configure_worker_threads(workers_, name_prefix, policy, priority,
623 register_workers_ ? &runtime_registry() : nullptr);
624 }
625
626 auto
628 {
629 return detail::configure_worker_threads(workers_, config, register_workers_ ? &runtime_registry() : nullptr);
630 }
631
633 auto
635 {
636 return detail::set_worker_affinity(workers_, affinity);
637 }
638
640 auto
645
647
650
652 void
654 {
655 if (is_current_worker())
657 std::unique_lock<std::mutex> lock(completion_mutex_);
658 completion_condition_.wait(lock, [this] { return outstanding_tasks_.load(std::memory_order_acquire) == 0; });
659 }
660
661 [[nodiscard]] auto
662 is_current_worker() const noexcept -> bool
663 {
664 return current_pool == this;
665 }
666
668
671
676 void
678 {
679 std::lock_guard<std::mutex> lock(trace_mutex_);
680 on_task_start_ = task_start_callback_storage(std::move(cb));
681 }
682
683 template <typename Callback,
684 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>, task_start_callback>, int> = 0>
685 void
686 set_on_task_start(Callback&& cb)
687 {
688 static_assert(std::is_invocable_r_v<void, Callback&, std::chrono::steady_clock::time_point, std::thread::id>,
689 "Task start callback must accept (time_point, std::thread::id)");
690 std::lock_guard<std::mutex> lock(trace_mutex_);
691 on_task_start_ = detail::make_copyable_function<void(std::chrono::steady_clock::time_point, std::thread::id)>(
692 std::forward<Callback>(cb));
693 }
694
700 void
702 {
703 std::lock_guard<std::mutex> lock(trace_mutex_);
704 on_task_end_ = task_end_callback_storage(std::move(cb));
705 }
706
707 template <typename Callback,
708 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>, task_end_callback>, int> = 0>
709 void
710 set_on_task_end(Callback&& cb)
711 {
712 static_assert(std::is_invocable_r_v<void, Callback&, std::chrono::steady_clock::time_point, std::thread::id,
713 std::chrono::microseconds>,
714 "Task end callback must accept (time_point, std::thread::id, "
715 "std::chrono::microseconds)");
716 std::lock_guard<std::mutex> lock(trace_mutex_);
717 on_task_end_ = detail::make_copyable_function<void(std::chrono::steady_clock::time_point, std::thread::id,
718 std::chrono::microseconds)>(std::forward<Callback>(cb));
719 }
720
722
723private:
724 size_t num_threads_;
725 bool register_workers_;
727 std::vector<detail::thread_backend> workers_;
728 std::vector<std::unique_ptr<work_stealing_deque<queued_task>>> worker_queues_;
729
730 std::queue<queued_task> overflow_tasks_;
731 mutable std::mutex overflow_mutex_;
732 mutable std::shared_timed_mutex submission_mutex_;
733 std::recursive_timed_mutex shutdown_mutex_;
734
735 std::atomic<bool> stop_;
736 std::atomic<bool> submissions_quiesced_{ false };
737 bool shutdown_completed_all_{ true };
738 std::chrono::steady_clock::time_point shutdown_completed_at_{};
739 std::condition_variable wakeup_condition_;
740 std::mutex wakeup_mutex_;
741
742 std::condition_variable completion_condition_;
743 std::mutex completion_mutex_;
744
745 std::atomic<size_t> next_victim_;
746 std::atomic<size_t> active_tasks_{ 0 };
747 std::atomic<size_t> outstanding_tasks_{ 0 };
748 std::atomic<size_t> completed_tasks_{ 0 };
749 std::atomic<size_t> stolen_tasks_{ 0 };
750 std::atomic<uint64_t> total_task_time_{ 0 };
751
752 std::mutex trace_mutex_;
753 task_start_callback_storage on_task_start_;
754 task_end_callback_storage on_task_end_;
755
756 std::chrono::steady_clock::time_point start_time_;
757 inline static thread_local work_stealing_pool_backend* current_pool = nullptr;
758
759 auto
760 discard_pending_tasks() -> size_t
761 {
762 size_t dropped_tasks = 0;
763 std::queue<queued_task> discarded_overflow;
764 {
765 std::lock_guard<std::mutex> lock(overflow_mutex_);
766 dropped_tasks += overflow_tasks_.size();
767 overflow_tasks_.swap(discarded_overflow);
768 }
769
770 if (dropped_tasks != 0)
771 {
772 std::lock_guard<std::mutex> lock(completion_mutex_);
773 outstanding_tasks_.fetch_sub(dropped_tasks, std::memory_order_acq_rel);
774 }
775
776 if (dropped_tasks != 0)
777 completion_condition_.notify_all();
778
779 if (dropped_tasks != 0)
780 {
781 std::queue<queued_task> empty;
782 discarded_overflow.swap(empty);
783 }
784
785 for (auto& queue : worker_queues_)
786 {
787 queued_task discarded;
788 while (queue->steal(discarded))
789 {
790 ++dropped_tasks;
791 {
792 std::lock_guard<std::mutex> lock(completion_mutex_);
793 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
794 }
795 completion_condition_.notify_all();
796 discarded = queued_task{};
797 }
798 }
799
800 return dropped_tasks;
801 }
802
803 auto
804 finish_shutdown(shutdown_policy_backend policy) -> bool
805 {
806 {
807 std::unique_lock<std::shared_timed_mutex> submission_lock(submission_mutex_);
808 submissions_quiesced_.store(true, std::memory_order_release);
809 }
810
811 size_t const dropped_tasks = policy == shutdown_policy_backend::drop_pending ? discard_pending_tasks() : size_t(0);
812
813 wakeup_condition_.notify_all();
814
815 for (auto& worker : workers_)
816 if (worker.joinable())
817 worker.join();
818
819 workers_.clear();
820 return dropped_tasks == 0;
821 }
822
823 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
824 void
825 worker_function(size_t worker_id)
826 {
827 detail::worker_context_guard<work_stealing_pool_backend> worker_context(current_pool, this);
828 std::optional<registration_guard_backend> reg_guard;
829 try
830 {
831 if (register_workers_)
832 reg_guard.emplace("hp_worker_" + std::to_string(worker_id), "threadschedule.pool");
833 startup_.arrive();
834 }
835 catch (...)
836 {
837 startup_.arrive(std::current_exception());
838 return;
839 }
840
841 thread_local std::mt19937 gen = [worker_id]()
842 {
843 try
844 {
845 std::random_device device;
846 return std::mt19937(device());
847 }
848 catch (...)
849 {
850 auto const seed = static_cast<std::mt19937::result_type>(
851 std::mt19937::default_seed ^ static_cast<std::mt19937::result_type>(worker_id));
852 return std::mt19937(seed);
853 }
854 }();
855
856 queued_task task;
857 std::uniform_int_distribution<size_t> dist(0, num_threads_ - 1);
858
859 while (true)
860 {
861 bool found_task = false;
862
863 if (worker_queues_[worker_id]->pop(task))
864 {
865 found_task = true;
866 }
867 else
868 {
869 size_t const max_steal_attempts = (std::min)(num_threads_, size_t(4));
870 for (size_t attempts = 0; attempts < max_steal_attempts; ++attempts)
871 {
872 size_t const victim_id = dist(gen);
873 if (victim_id != worker_id && worker_queues_[victim_id]->steal(task))
874 {
875 found_task = true;
876 stolen_tasks_.fetch_add(1, std::memory_order_relaxed);
877 break;
878 }
879 }
880 }
881
882 if (!found_task)
883 {
884 std::lock_guard<std::mutex> lock(overflow_mutex_);
885 if (!overflow_tasks_.empty())
886 {
887 task = std::move(overflow_tasks_.front());
888 overflow_tasks_.pop();
889 found_task = true;
890 }
891 }
892
893 if (found_task)
894 {
895 active_tasks_.fetch_add(1, std::memory_order_relaxed);
896
897 auto const start_time = std::chrono::steady_clock::now();
898 auto const tid = std::this_thread::get_id();
899
900 try
901 {
902 task_start_callback_storage on_task_start;
903 {
904 std::lock_guard<std::mutex> tl(trace_mutex_);
905 on_task_start = on_task_start_;
906 }
907 if (on_task_start)
908 on_task_start(start_time, tid);
909 }
910 catch (...)
911 {
912 }
913
914 // For submit() tasks the callable is a packaged_task which
915 // catches exceptions internally and stores them in the
916 // std::future shared state - those never reach this catch.
917 // For post() tasks (fire-and-forget) the catch prevents an
918 // unhandled exception from terminating the worker thread.
919 try
920 {
921 task();
922 }
923 catch (...)
924 {
925 }
926 task = queued_task{};
927 auto const end_time = std::chrono::steady_clock::now();
928
929 auto const task_duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
930 total_task_time_.fetch_add(task_duration.count(), std::memory_order_relaxed);
931
932 try
933 {
934 task_end_callback_storage on_task_end;
935 {
936 std::lock_guard<std::mutex> tl(trace_mutex_);
937 on_task_end = on_task_end_;
938 }
939 if (on_task_end)
940 on_task_end(end_time, tid, task_duration);
941 }
942 catch (...)
943 {
944 }
945
946 active_tasks_.fetch_sub(1, std::memory_order_relaxed);
947 {
948 std::lock_guard<std::mutex> lock(completion_mutex_);
949 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
950 }
951 completed_tasks_.fetch_add(1, std::memory_order_relaxed);
952
953 completion_condition_.notify_all();
954 wakeup_condition_.notify_all();
955 }
956 else
957 {
958 if (stop_.load(std::memory_order_acquire) && submissions_quiesced_.load(std::memory_order_acquire)
959 && outstanding_tasks_.load(std::memory_order_acquire) == 0)
960 {
961 break;
962 }
963
964 std::unique_lock<std::mutex> lock(wakeup_mutex_);
965 wakeup_condition_.wait_for(lock, std::chrono::microseconds(100));
966 }
967 }
968 }
969};
970
971} // namespace threadschedule::detail
Manages a set of CPU indices to which a thread may be bound.
Definition native.hpp:433
Value-semantic wrapper for a thread scheduling priority.
Definition native.hpp:140
static constexpr auto normal() noexcept -> native_thread_priority
Definition native.hpp:164
High-performance thread pool optimized for high-frequency task submission.
auto try_post(F &&f, Args &&... args) -> expected< void, std::error_code >
Fire-and-forget task submission (non-throwing variant).
auto submit_batch(Iterator begin, Iterator end) -> std::vector< std::future< void > >
Submit a range of void() callables in one go (throwing).
auto submit(F &&f, Args &&... args) -> std::future< bind_result_t< F, Args... > >
Submit a task, throwing on shutdown.
void wait_for_tasks()
Block until all pending and active tasks have completed.
void post(F &&f, Args &&... args)
Fire-and-forget task submission (throwing variant).
void set_on_task_start(task_start_callback cb)
Register a callback invoked just before each task executes.
auto set_affinity(native_thread_affinity const &affinity) -> expected< void, std::error_code >
Pin all workers to the same CPU set.
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 size() const noexcept -> size_t
Number of worker threads in this pool.
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).
auto shutdown_for(std::chrono::milliseconds timeout) -> bool
Attempt a timed drain: finish as many tasks as possible within timeout, then discard queued work.
void shutdown(shutdown_policy_backend policy=shutdown_policy_backend::drain)
Shut the pool down.
auto get_statistics() const -> statistics
Collect approximate performance counters.
auto configure_threads(native_thread_config const &config) -> expected< void, std::error_code >
work_stealing_pool_backend(work_stealing_pool_backend const &)=delete
work_stealing_pool_backend(size_t num_threads=default_worker_count(), size_t deque_capacity=work_stealing_deque< queued_task >::default_capacity, bool register_workers=false)
auto operator=(work_stealing_pool_backend const &) -> work_stealing_pool_backend &=delete
auto pending_tasks() const -> size_t
Approximate count of tasks waiting in all queues.
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.
void parallel_for_each(Iterator begin, Iterator end, F &&func)
Apply func to every element in [begin, end) in parallel.
void set_on_task_end(task_end_callback cb)
Register a callback invoked just after each task completes.
auto distribute_across_cpus() -> expected< void, std::error_code >
Pin each worker to a distinct CPU core (round-robin).
work_stealing_pool_backend(size_t num_threads, Bool register_workers)
constexpr auto has_value() const noexcept -> bool
Definition expected.hpp:599
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.
Definition native.hpp:85
@ 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
Definition callbacks.hpp:15
auto multipass_range_size(Iterator begin, Iterator end) -> size_t
auto make_move_only_function(Callable &&callable) -> move_only_function< Signature, InlineSize >
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
task_end_callback task_end_callback_storage
Definition callbacks.hpp:16
copyable_function< void(std::chrono::steady_clock::time_point, std::thread::id)> task_start_callback
Definition callbacks.hpp:11
auto default_worker_count() noexcept -> std::size_t
auto bind_args(F &&function, Args &&... args)
Definition bind.hpp:17
auto checked_worker_count(std::size_t count) -> std::size_t
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
Definition callbacks.hpp:13
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
Definition deadline.hpp:11
expected< T, std::error_code > result
Standard result type used by public APIs.
Definition result.hpp:22
Internal queued-task shutdown behavior.
Bounded owner/thief queue used by the work-stealing pool.
Worker identity, CPU selection, and registration helpers.
Worker-thread count configuration for pool types.