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())
145 worker_queues_.resize(num_threads_);
146 for (
size_t i = 0; i < num_threads_; ++i)
148 worker_queues_[i] = std::make_unique<work_stealing_deque<queued_task>>(deque_capacity);
151 workers_.reserve(num_threads_);
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_);
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())
171 template <
typename Bool, std::enable_if_t<std::is_same_v<std::decay_t<Bool>,
bool>,
int> = 0>
196 std::lock_guard<std::recursive_timed_mutex> shutdown_lock(shutdown_mutex_);
197 if (workers_.empty())
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();
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))
227 if (workers_.empty())
228 return shutdown_completed_all_ && shutdown_completed_at_ <= deadline;
230 stop_.store(
true, std::memory_order_release);
231 if (!submissions_quiesced_.load(std::memory_order_acquire))
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))
238 submissions_quiesced_.store(
true, std::memory_order_release);
240 wakeup_condition_.notify_all();
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; });
249 size_t const dropped_tasks = discard_pending_tasks();
250 shutdown_completed_all_ = shutdown_completed_all_ && dropped_tasks == 0;
253 wakeup_condition_.notify_all();
254 completion_condition_.notify_all();
256 if (deadline == std::chrono::steady_clock::time_point::max())
258 for (
auto& worker : workers_)
259 if (worker.joinable())
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_;
284 template <
typename F,
typename... Args>
290 auto task = std::make_shared<std::packaged_task<return_type()>>(
293 std::future<return_type>
result = task->get_future();
296 std::shared_lock<std::shared_timed_mutex> submission_lock(submission_mutex_);
298 if (stop_.load(std::memory_order_acquire))
299 return unexpected(std::make_error_code(std::errc::operation_canceled));
301 size_t const preferred_queue = next_victim_.fetch_add(1, std::memory_order_relaxed) % num_threads_;
303 outstanding_tasks_.fetch_add(1, std::memory_order_release);
304 if (worker_queues_[preferred_queue]->push(std::move(queued)))
306 wakeup_condition_.notify_one();
309 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
311 for (
size_t attempts = 0; attempts < (std::min)(num_threads_,
size_t(3)); ++attempts)
313 size_t const idx = (preferred_queue + attempts + 1) % num_threads_;
314 outstanding_tasks_.fetch_add(1, std::memory_order_release);
317 if (worker_queues_[idx]->push(std::move(queued)))
319 wakeup_condition_.notify_one();
322 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
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));
331 overflow_tasks_.emplace(std::move(queued));
332 outstanding_tasks_.fetch_add(1, std::memory_order_release);
335 wakeup_condition_.notify_all();
348 template <
typename F,
typename... Args>
354 throw std::runtime_error(
"work_stealing_pool_backend is shutting down");
368 template <
typename F,
typename... Args>
372 auto r =
try_post(std::forward<F>(f), std::forward<Args>(args)...);
374 throw std::runtime_error(
"work_stealing_pool_backend is shutting down");
383 template <
typename F,
typename... Args>
390 std::shared_lock<std::shared_timed_mutex> submission_lock(submission_mutex_);
392 if (stop_.load(std::memory_order_acquire))
393 return unexpected(std::make_error_code(std::errc::operation_canceled));
395 size_t const preferred_queue = next_victim_.fetch_add(1, std::memory_order_relaxed) % num_threads_;
397 outstanding_tasks_.fetch_add(1, std::memory_order_release);
398 if (worker_queues_[preferred_queue]->push(std::move(bound)))
400 wakeup_condition_.notify_one();
403 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
405 for (
size_t attempts = 0; attempts < (std::min)(num_threads_,
size_t(3)); ++attempts)
407 size_t const idx = (preferred_queue + attempts + 1) % num_threads_;
408 outstanding_tasks_.fetch_add(1, std::memory_order_release);
412 if (worker_queues_[idx]->push(std::move(bound)))
414 wakeup_condition_.notify_one();
417 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
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));
426 overflow_tasks_.emplace(std::move(bound));
427 outstanding_tasks_.fetch_add(1, std::memory_order_release);
430 wakeup_condition_.notify_all();
446 template <
typename Iterator>
450 std::vector<std::future<void>> futures;
452 futures.reserve(batch_size_hint);
453 std::vector<queued_task> prepared;
454 prepared.reserve(batch_size_hint);
456 for (
auto it = begin; it != end; ++it)
458 auto task = std::make_shared<std::packaged_task<void()>>(*it);
459 futures.push_back(task->get_future());
460 prepared.emplace_back([task]() { (*task)(); });
463 size_t const batch_size = prepared.size();
465 std::shared_lock<std::shared_timed_mutex> submission_lock(submission_mutex_);
467 if (stop_.load(std::memory_order_acquire))
468 return unexpected(std::make_error_code(std::errc::operation_canceled));
470 size_t queue_idx = next_victim_.fetch_add(batch_size, std::memory_order_relaxed) % num_threads_;
474 for (
auto& queued : prepared)
476 bool enqueued =
false;
477 for (
size_t attempts = 0; attempts < num_threads_; ++attempts)
479 outstanding_tasks_.fetch_add(1, std::memory_order_release);
482 if (worker_queues_[queue_idx]->push(std::move(queued)))
485 queue_idx = (queue_idx + 1) % num_threads_;
488 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
489 queue_idx = (queue_idx + 1) % num_threads_;
494 std::lock_guard<std::mutex> lock(overflow_mutex_);
497 overflow_tasks_.emplace(std::move(queued));
498 outstanding_tasks_.fetch_add(1, std::memory_order_release);
504 wakeup_condition_.notify_all();
508 wakeup_condition_.notify_all();
517 template <
typename Iterator>
519 submit_batch(Iterator begin, Iterator end) -> std::vector<std::future<void>>
523 throw std::runtime_error(
"work_stealing_pool_backend is shutting down");
534 template <
typename Iterator,
typename F>
548 size() const noexcept ->
size_t
558 for (
auto const& queue : worker_queues_)
560 total += queue->size();
563 std::lock_guard<std::mutex> lock(overflow_mutex_);
564 total += overflow_tasks_.size();
572 auto const now = std::chrono::steady_clock::now();
573 auto const elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - start_time_);
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);
582 if (elapsed.count() > 0)
591 auto const total_task_time = total_task_time_.load(std::memory_order_acquire);
657 std::unique_lock<std::mutex> lock(completion_mutex_);
658 completion_condition_.wait(lock, [
this] {
return outstanding_tasks_.load(std::memory_order_acquire) == 0; });
664 return current_pool ==
this;
679 std::lock_guard<std::mutex> lock(trace_mutex_);
683 template <
typename Callback,
684 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>,
task_start_callback>,
int> = 0>
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));
703 std::lock_guard<std::mutex> lock(trace_mutex_);
707 template <
typename Callback,
708 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>,
task_end_callback>,
int> = 0>
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_);
718 std::chrono::microseconds)>(std::forward<Callback>(cb));
725 bool register_workers_;
727 std::vector<detail::thread_backend> workers_;
728 std::vector<std::unique_ptr<work_stealing_deque<queued_task>>> worker_queues_;
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_;
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_;
742 std::condition_variable completion_condition_;
743 std::mutex completion_mutex_;
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 };
752 std::mutex trace_mutex_;
756 std::chrono::steady_clock::time_point start_time_;
760 discard_pending_tasks() ->
size_t
762 size_t dropped_tasks = 0;
763 std::queue<queued_task> discarded_overflow;
765 std::lock_guard<std::mutex> lock(overflow_mutex_);
766 dropped_tasks += overflow_tasks_.size();
767 overflow_tasks_.swap(discarded_overflow);
770 if (dropped_tasks != 0)
772 std::lock_guard<std::mutex> lock(completion_mutex_);
773 outstanding_tasks_.fetch_sub(dropped_tasks, std::memory_order_acq_rel);
776 if (dropped_tasks != 0)
777 completion_condition_.notify_all();
779 if (dropped_tasks != 0)
781 std::queue<queued_task> empty;
782 discarded_overflow.swap(empty);
785 for (
auto& queue : worker_queues_)
788 while (queue->steal(discarded))
792 std::lock_guard<std::mutex> lock(completion_mutex_);
793 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
795 completion_condition_.notify_all();
800 return dropped_tasks;
807 std::unique_lock<std::shared_timed_mutex> submission_lock(submission_mutex_);
808 submissions_quiesced_.store(
true, std::memory_order_release);
813 wakeup_condition_.notify_all();
815 for (
auto& worker : workers_)
816 if (worker.joinable())
820 return dropped_tasks == 0;
825 worker_function(
size_t worker_id)
827 detail::worker_context_guard<work_stealing_pool_backend> worker_context(current_pool,
this);
828 std::optional<registration_guard_backend> reg_guard;
831 if (register_workers_)
832 reg_guard.emplace(
"hp_worker_" + std::to_string(worker_id),
"threadschedule.pool");
837 startup_.
arrive(std::current_exception());
841 thread_local std::mt19937 gen = [worker_id]()
845 std::random_device device;
846 return std::mt19937(device());
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);
857 std::uniform_int_distribution<size_t> dist(0, num_threads_ - 1);
861 bool found_task =
false;
863 if (worker_queues_[worker_id]->pop(task))
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)
872 size_t const victim_id = dist(gen);
873 if (victim_id != worker_id && worker_queues_[victim_id]->steal(task))
876 stolen_tasks_.fetch_add(1, std::memory_order_relaxed);
884 std::lock_guard<std::mutex> lock(overflow_mutex_);
885 if (!overflow_tasks_.empty())
887 task = std::move(overflow_tasks_.front());
888 overflow_tasks_.pop();
895 active_tasks_.fetch_add(1, std::memory_order_relaxed);
897 auto const start_time = std::chrono::steady_clock::now();
898 auto const tid = std::this_thread::get_id();
904 std::lock_guard<std::mutex> tl(trace_mutex_);
905 on_task_start = on_task_start_;
908 on_task_start(start_time, tid);
927 auto const end_time = std::chrono::steady_clock::now();
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);
936 std::lock_guard<std::mutex> tl(trace_mutex_);
937 on_task_end = on_task_end_;
940 on_task_end(end_time, tid, task_duration);
946 active_tasks_.fetch_sub(1, std::memory_order_relaxed);
948 std::lock_guard<std::mutex> lock(completion_mutex_);
949 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
951 completed_tasks_.fetch_add(1, std::memory_order_relaxed);
953 completion_condition_.notify_all();
954 wakeup_condition_.notify_all();
958 if (stop_.load(std::memory_order_acquire) && submissions_quiesced_.load(std::memory_order_acquire)
959 && outstanding_tasks_.load(std::memory_order_acquire) == 0)
964 std::unique_lock<std::mutex> lock(wakeup_mutex_);
965 wakeup_condition_.wait_for(lock, std::chrono::microseconds(100));
Worker identity, CPU selection, and registration helpers.