16#include <condition_variable>
28#if __cpp_lib_ranges >= 201911L
38template <
typename WorkerRange>
43 for (
size_t i = 0; i < workers.size(); ++i)
45 std::string
const thread_name = name_prefix +
"_" + std::to_string(i);
46 if (!workers[i].set_name(thread_name).has_value())
48 if (!workers[i].set_scheduling_policy(policy, priority).has_value())
53 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
56template <
typename WorkerRange>
60 for (
auto& worker : workers)
62 if (!worker.set_affinity(affinity).has_value())
67 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
70template <
typename WorkerRange>
73 auto const cpu_count = std::thread::hardware_concurrency();
75 return unexpected(std::make_error_code(std::errc::invalid_argument));
78 for (
size_t i = 0; i < workers.size(); ++i)
81 if (!workers[i].set_affinity(affinity).has_value())
86 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
89template <
typename Pool,
typename Iterator,
typename F>
92 auto const total =
static_cast<size_t>(std::distance(begin, end));
96 size_t const chunk_size = (std::max)(
size_t(1), total / (num_workers * 4));
97 std::vector<std::future<void>> futures;
102 auto remaining =
static_cast<size_t>(std::distance(it, end));
103 auto this_chunk = (std::min)(chunk_size, remaining);
105 std::advance(chunk_end, this_chunk);
107 futures.push_back(pool.submit([it, chunk_end, &func]() {
108 for (auto cur = it; cur != chunk_end; ++cur)
115 for (
auto& f : futures)
130template <
typename F,
typename... Args>
133#if __cpp_init_captures >= 201803L
134 return [fn = std::forward<F>(f), ... a = std::forward<Args>(args)]()
mutable {
return fn(std::move(a)...); };
136 return [fn = std::forward<F>(f), tup = std::make_tuple(std::forward<Args>(args)...)]()
mutable {
137 return std::apply(std::move(fn), std::move(tup));
179template <
size_t TaskSize = 64>
182 static_assert(TaskSize >
sizeof(
void*),
"TaskSize must be larger than a pointer");
186 void (*invoke)(
void* storage);
187 void (*destroy)(
void* storage);
188 void (*move_to)(
void* dst,
void* src)
noexcept;
191 static constexpr size_t kBufferSize = TaskSize -
sizeof(VTable
const*);
193 template <
typename F>
194 static constexpr bool fits_inline_v =
195 sizeof(F) <= kBufferSize &&
alignof(F) <=
alignof(std::max_align_t) && std::is_nothrow_move_constructible_v<F>;
197 template <
typename F>
198 static VTable
const* vtable_for()
noexcept
200 if constexpr (fits_inline_v<F>)
202 static constexpr VTable vt{[](
void* s) { (*
static_cast<F*
>(s))(); },
203 [](
void* s) {
static_cast<F*
>(s)->~F(); },
204 [](
void* dst,
void* src)
noexcept {
205 ::new (dst) F(std::move(*
static_cast<F*
>(src)));
206 static_cast<F*
>(src)->~F();
212 static constexpr VTable vt{[](
void* s) { (*(*
static_cast<F**
>(s)))(); },
213 [](
void* s) {
delete *
static_cast<F**
>(s); },
214 [](
void* dst,
void* src)
noexcept {
215 *
static_cast<F**
>(dst) = *
static_cast<F**
>(src);
216 *
static_cast<F**
>(src) =
nullptr;
225 template <
typename F,
typename = std::enable_if_t<!std::is_same_v<std::decay_t<F>, SboCallable>>>
228 using Decay = std::decay_t<F>;
229 vtable_ = vtable_for<Decay>();
230 if constexpr (fits_inline_v<Decay>)
231 ::new (buffer_) Decay(std::forward<F>(f));
233 *
reinterpret_cast<Decay**
>(buffer_) =
new Decay(std::forward<F>(f));
240 vtable_->move_to(buffer_, other.buffer_);
241 other.vtable_ =
nullptr;
250 vtable_->destroy(buffer_);
251 vtable_ = other.vtable_;
254 vtable_->move_to(buffer_, other.buffer_);
255 other.vtable_ =
nullptr;
267 vtable_->destroy(buffer_);
270 explicit operator bool() const noexcept
272 return vtable_ !=
nullptr;
280 vt->destroy(buffer_);
284 VTable
const* vtable_ =
nullptr;
285 alignas(std::max_align_t)
unsigned char buffer_[kBufferSize];
326 std::chrono::microseconds elapsed)>;
339 struct alignas(CACHE_LINE_SIZE) AlignedItem
342 AlignedItem() =
default;
343 AlignedItem(T&& t) : item(std::move(t))
346 template <
typename U = T, std::enable_if_t<std::is_copy_constructible_v<U>,
int> = 0>
347 AlignedItem(T
const& t) : item(t)
352 std::unique_ptr<AlignedItem[]> buffer_;
355 alignas(CACHE_LINE_SIZE) std::atomic<size_t> top_{0};
356 alignas(CACHE_LINE_SIZE) std::atomic<size_t> bottom_{0};
357 alignas(CACHE_LINE_SIZE)
mutable std::mutex mutex_;
361 : buffer_(std::make_unique<AlignedItem[]>(capacity)), capacity_(capacity)
365 [[nodiscard]]
auto push(T&& item) ->
bool
367 std::lock_guard<std::mutex> lock(mutex_);
368 size_t const t = top_.load(std::memory_order_relaxed);
369 size_t const b = bottom_.load(std::memory_order_relaxed);
371 if (t - b >= capacity_)
376 buffer_[t % capacity_] = AlignedItem(std::move(item));
377 top_.store(t + 1, std::memory_order_release);
381 template <
typename U = T, std::enable_if_t<std::is_copy_constructible_v<U>,
int> = 0>
382 [[nodiscard]]
auto push(T
const& item) ->
bool
384 std::lock_guard<std::mutex> lock(mutex_);
385 size_t const t = top_.load(std::memory_order_relaxed);
386 size_t const b = bottom_.load(std::memory_order_relaxed);
388 if (t - b >= capacity_)
393 buffer_[t % capacity_] = AlignedItem(item);
394 top_.store(t + 1, std::memory_order_release);
398 [[nodiscard]]
auto pop(T& item) ->
bool
400 std::lock_guard<std::mutex> lock(mutex_);
401 size_t const t = top_.load(std::memory_order_relaxed);
402 size_t const b = bottom_.load(std::memory_order_relaxed);
409 size_t const new_top = t - 1;
410 item = std::move(buffer_[new_top % capacity_].item);
411 top_.store(new_top, std::memory_order_relaxed);
415 [[nodiscard]]
auto steal(T& item) ->
bool
417 std::lock_guard<std::mutex> lock(mutex_);
418 size_t const b = bottom_.load(std::memory_order_relaxed);
419 size_t const t = top_.load(std::memory_order_relaxed);
426 item = std::move(buffer_[b % capacity_].item);
427 bottom_.store(b + 1, std::memory_order_relaxed);
431 [[nodiscard]]
auto size() const ->
size_t
433 size_t const t = top_.load(std::memory_order_relaxed);
434 size_t const b = bottom_.load(std::memory_order_relaxed);
435 return t > b ? t - b : 0;
438 [[nodiscard]]
auto empty() const ->
bool
445 std::lock_guard<std::mutex> lock(mutex_);
446 bottom_.store(0, std::memory_order_relaxed);
447 top_.store(0, std::memory_order_relaxed);
452 std::lock_guard<std::mutex> lock(mutex_);
453 size_t const t = top_.load(std::memory_order_relaxed);
454 size_t const b = bottom_.load(std::memory_order_relaxed);
455 size_t const count = t > b ? t - b : 0;
456 bottom_.store(0, std::memory_order_relaxed);
457 top_.store(0, std::memory_order_relaxed);
565 using Task = std::function<void()>;
581 bool register_workers =
false)
582 : num_threads_(num_threads == 0 ? 1 : num_threads), register_workers_(register_workers), stop_(false),
583 next_victim_(0), start_time_(std::chrono::steady_clock::now())
585 worker_queues_.resize(num_threads_);
586 for (
size_t i = 0; i < num_threads_; ++i)
588 worker_queues_[i] = std::make_unique<WorkStealingDeque<QueuedTask>>(deque_capacity);
591 workers_.reserve(num_threads_);
593 for (
size_t i = 0; i < num_threads_; ++i)
595 workers_.emplace_back(&HighPerformancePool::worker_function,
this, i);
615 size_t dropped_tasks = 0;
617 std::lock_guard<std::mutex> lock(overflow_mutex_);
618 if (stop_.exchange(
true, std::memory_order_acq_rel))
623 dropped_tasks += overflow_tasks_.size();
624 std::queue<QueuedTask> empty;
625 overflow_tasks_.swap(empty);
626 for (
auto& q : worker_queues_)
627 dropped_tasks += q->clear_and_count();
631 if (dropped_tasks != 0)
633 std::lock_guard<std::mutex> lock(completion_mutex_);
634 outstanding_tasks_.fetch_sub(dropped_tasks, std::memory_order_acq_rel);
637 if (dropped_tasks != 0)
638 completion_condition_.notify_all();
639 wakeup_condition_.notify_all();
641 for (
auto& worker : workers_)
643 if (worker.joinable())
658 auto const deadline = std::chrono::steady_clock::now() + timeout;
661 std::lock_guard<std::mutex> lock(overflow_mutex_);
662 if (stop_.load(std::memory_order_acquire))
666 std::unique_lock<std::mutex> lock(completion_mutex_);
667 bool const drained = completion_condition_.wait_until(
668 lock, deadline, [
this] {
return outstanding_tasks_.load(std::memory_order_acquire) == 0; });
690 template <
typename F,
typename... Args>
691 auto try_submit(F&& f, Args&&... args) ->
expected<std::future<std::invoke_result_t<F, Args...>>, std::error_code>
693 using return_type = std::invoke_result_t<F, Args...>;
695 auto task = std::make_shared<std::packaged_task<return_type()>>(
698 std::future<return_type> result = task->get_future();
700 if (stop_.load(std::memory_order_acquire))
701 return unexpected(std::make_error_code(std::errc::operation_canceled));
703 size_t const preferred_queue = next_victim_.fetch_add(1, std::memory_order_relaxed) % num_threads_;
705 outstanding_tasks_.fetch_add(1, std::memory_order_release);
706 if (worker_queues_[preferred_queue]->push([task]() { (*task)(); }))
708 wakeup_condition_.notify_one();
711 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
713 for (
size_t attempts = 0; attempts < (std::min)(num_threads_, size_t(3)); ++attempts)
715 size_t const idx = (preferred_queue + attempts + 1) % num_threads_;
716 outstanding_tasks_.fetch_add(1, std::memory_order_release);
717 if (worker_queues_[idx]->push([task]() { (*task)(); }))
719 wakeup_condition_.notify_one();
722 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
726 std::lock_guard<std::mutex> lock(overflow_mutex_);
727 if (stop_.load(std::memory_order_relaxed))
728 return unexpected(std::make_error_code(std::errc::operation_canceled));
729 outstanding_tasks_.fetch_add(1, std::memory_order_release);
730 overflow_tasks_.emplace([task]() { (*task)(); });
733 wakeup_condition_.notify_all();
746 template <
typename F,
typename... Args>
747 auto submit(F&& f, Args&&... args) -> std::future<std::invoke_result_t<F, Args...>>
749 auto result =
try_submit(std::forward<F>(f), std::forward<Args>(args)...);
750 if (!result.has_value())
751 throw std::runtime_error(
"HighPerformancePool is shutting down");
752 return std::move(result.value());
765 template <
typename F,
typename... Args>
766 void post(F&& f, Args&&... args)
768 auto r =
try_post(std::forward<F>(f), std::forward<Args>(args)...);
770 throw std::runtime_error(
"HighPerformancePool is shutting down");
779 template <
typename F,
typename... Args>
785 if (stop_.load(std::memory_order_acquire))
786 return unexpected(std::make_error_code(std::errc::operation_canceled));
788 size_t const preferred_queue = next_victim_.fetch_add(1, std::memory_order_relaxed) % num_threads_;
790 outstanding_tasks_.fetch_add(1, std::memory_order_release);
791 if (worker_queues_[preferred_queue]->push(std::move(bound)))
793 wakeup_condition_.notify_one();
796 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
798 for (
size_t attempts = 0; attempts < (std::min)(num_threads_, size_t(3)); ++attempts)
800 size_t const idx = (preferred_queue + attempts + 1) % num_threads_;
801 outstanding_tasks_.fetch_add(1, std::memory_order_release);
802 if (worker_queues_[idx]->push(std::move(bound)))
804 wakeup_condition_.notify_one();
807 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
811 std::lock_guard<std::mutex> lock(overflow_mutex_);
812 if (stop_.load(std::memory_order_relaxed))
813 return unexpected(std::make_error_code(std::errc::operation_canceled));
814 outstanding_tasks_.fetch_add(1, std::memory_order_release);
815 overflow_tasks_.emplace(std::move(bound));
818 wakeup_condition_.notify_all();
822#if __cpp_lib_jthread >= 201911L
829 template <
typename F,
typename... Args>
830 auto submit(std::stop_token token, F&& f, Args&&... args) -> std::future<std::invoke_result_t<F, Args...>>
832 return submit([token = std::move(token),
833 bound =
detail::bind_args(std::forward<F>(f), std::forward<Args>(args)...)]()
mutable {
834 if (token.stop_requested())
835 return std::invoke_result_t<F, Args...>();
841 template <
typename F,
typename... Args>
842 auto try_submit(std::stop_token token, F&& f, Args&&... args)
843 -> expected<std::future<std::invoke_result_t<F, Args...>>, std::error_code>
845 return try_submit([token = std::move(token),
846 bound = detail::bind_args(std::forward<F>(f), std::forward<Args>(args)...)]()
mutable {
847 if (token.stop_requested())
848 return std::invoke_result_t<F, Args...>();
865 template <
typename Iterator>
868 std::vector<std::future<void>> futures;
869 size_t const batch_size = std::distance(begin, end);
870 futures.reserve(batch_size);
872 if (stop_.load(std::memory_order_acquire))
873 return unexpected(std::make_error_code(std::errc::operation_canceled));
875 size_t queue_idx = next_victim_.fetch_add(batch_size, std::memory_order_relaxed) % num_threads_;
877 for (
auto it = begin; it != end; ++it)
879 auto task = std::make_shared<std::packaged_task<void()>>(*it);
880 futures.push_back(task->get_future());
883 for (
size_t attempts = 0; attempts < num_threads_; ++attempts)
885 outstanding_tasks_.fetch_add(1, std::memory_order_release);
886 if (worker_queues_[queue_idx]->push([task]() { (*task)(); }))
891 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
892 queue_idx = (queue_idx + 1) % num_threads_;
897 std::lock_guard<std::mutex> lock(overflow_mutex_);
898 outstanding_tasks_.fetch_add(1, std::memory_order_release);
899 overflow_tasks_.emplace([task]() { (*task)(); });
903 wakeup_condition_.notify_all();
912 template <
typename Iterator>
913 auto submit_batch(Iterator begin, Iterator end) -> std::vector<std::future<void>>
916 if (!result.has_value())
917 throw std::runtime_error(
"HighPerformancePool is shutting down");
918 return std::move(result.value());
927 template <
typename Iterator,
typename F>
933#if __cpp_lib_ranges >= 201911L
935 template <std::ranges::input_range R>
936 auto submit_batch(R&& range)
938 return submit_batch(std::ranges::begin(range), std::ranges::end(range));
941 template <std::ranges::input_range R>
942 auto try_submit_batch(R&& range)
944 return try_submit_batch(std::ranges::begin(range), std::ranges::end(range));
947 template <std::ranges::input_range R,
typename F>
950 parallel_for_each(std::ranges::begin(range), std::ranges::end(range), std::forward<F>(func));
959 [[nodiscard]]
auto size() const noexcept ->
size_t
968 for (
auto const& queue : worker_queues_)
970 total += queue->size();
973 std::lock_guard<std::mutex> lock(overflow_mutex_);
974 total += overflow_tasks_.size();
981 auto const now = std::chrono::steady_clock::now();
982 auto const elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - start_time_);
986 stats.
active_threads = active_tasks_.load(std::memory_order_acquire);
988 stats.
completed_tasks = completed_tasks_.load(std::memory_order_acquire);
989 stats.
stolen_tasks = stolen_tasks_.load(std::memory_order_acquire);
991 if (elapsed.count() > 0)
1000 auto const total_task_time = total_task_time_.load(std::memory_order_acquire);
1052 std::unique_lock<std::mutex> lock(completion_mutex_);
1053 completion_condition_.wait(lock, [
this] {
return outstanding_tasks_.load(std::memory_order_acquire) == 0; });
1067 std::lock_guard<std::mutex> lock(trace_mutex_);
1071 template <
typename Callback,
1072 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>,
TaskStartCallback>,
int> = 0>
1075 static_assert(std::is_invocable_r_v<void, Callback&, std::chrono::steady_clock::time_point, std::thread::id>,
1076 "Task start callback must accept (time_point, std::thread::id)");
1077 std::lock_guard<std::mutex> lock(trace_mutex_);
1079 std::forward<Callback>(cb));
1089 std::lock_guard<std::mutex> lock(trace_mutex_);
1093 template <
typename Callback,
1094 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>,
TaskEndCallback>,
int> = 0>
1098 std::is_invocable_r_v<void, Callback&, std::chrono::steady_clock::time_point, std::thread::id,
1099 std::chrono::microseconds>,
1100 "Task end callback must accept (time_point, std::thread::id, std::chrono::microseconds)");
1101 std::lock_guard<std::mutex> lock(trace_mutex_);
1104 std::chrono::microseconds)>(std::forward<Callback>(cb));
1110 size_t num_threads_;
1111 bool register_workers_;
1112 std::vector<ThreadWrapper> workers_;
1113 std::vector<std::unique_ptr<WorkStealingDeque<QueuedTask>>> worker_queues_;
1115 std::queue<QueuedTask> overflow_tasks_;
1116 mutable std::mutex overflow_mutex_;
1118 std::atomic<bool> stop_;
1119 std::condition_variable wakeup_condition_;
1120 std::mutex wakeup_mutex_;
1122 std::condition_variable completion_condition_;
1123 std::mutex completion_mutex_;
1125 std::atomic<size_t> next_victim_;
1126 std::atomic<size_t> active_tasks_{0};
1127 std::atomic<size_t> outstanding_tasks_{0};
1128 std::atomic<size_t> completed_tasks_{0};
1129 std::atomic<size_t> stolen_tasks_{0};
1130 std::atomic<uint64_t> total_task_time_{0};
1132 std::mutex trace_mutex_;
1136 std::chrono::steady_clock::time_point start_time_;
1139 void worker_function(
size_t worker_id)
1141 std::optional<AutoRegisterCurrentThread> reg_guard;
1142 if (register_workers_)
1143 reg_guard.emplace(
"hp_worker_" + std::to_string(worker_id),
"threadschedule.pool");
1145 thread_local std::mt19937 gen = []() {
1146 std::random_device device;
1147 return std::mt19937(device());
1151 std::uniform_int_distribution<size_t> dist(0, num_threads_ - 1);
1155 bool found_task =
false;
1157 if (worker_queues_[worker_id]->pop(task))
1163 size_t const max_steal_attempts = (std::min)(num_threads_,
size_t(4));
1164 for (
size_t attempts = 0; attempts < max_steal_attempts; ++attempts)
1166 size_t const victim_id = dist(gen);
1167 if (victim_id != worker_id && worker_queues_[victim_id]->steal(task))
1170 stolen_tasks_.fetch_add(1, std::memory_order_relaxed);
1178 std::lock_guard<std::mutex> lock(overflow_mutex_);
1179 if (!overflow_tasks_.empty())
1181 task = std::move(overflow_tasks_.front());
1182 overflow_tasks_.pop();
1189 active_tasks_.fetch_add(1, std::memory_order_relaxed);
1191 auto const start_time = std::chrono::steady_clock::now();
1192 auto const tid = std::this_thread::get_id();
1196 std::lock_guard<std::mutex> tl(trace_mutex_);
1197 on_task_start = on_task_start_;
1200 on_task_start(start_time, tid);
1214 auto const end_time = std::chrono::steady_clock::now();
1216 auto const task_duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
1217 total_task_time_.fetch_add(task_duration.count(), std::memory_order_relaxed);
1221 std::lock_guard<std::mutex> tl(trace_mutex_);
1222 on_task_end = on_task_end_;
1225 on_task_end(end_time, tid, task_duration);
1227 active_tasks_.fetch_sub(1, std::memory_order_relaxed);
1229 std::lock_guard<std::mutex> lock(completion_mutex_);
1230 outstanding_tasks_.fetch_sub(1, std::memory_order_acq_rel);
1232 completed_tasks_.fetch_add(1, std::memory_order_relaxed);
1234 completion_condition_.notify_all();
1235 wakeup_condition_.notify_all();
1239 if (stop_.load(std::memory_order_acquire)
1240 && outstanding_tasks_.load(std::memory_order_acquire) == 0)
1245 std::unique_lock<std::mutex> lock(wakeup_mutex_);
1246 wakeup_condition_.wait_for(lock, std::chrono::microseconds(100));
1264 template <
typename Lock,
typename Pred>
1265 static auto wait(std::condition_variable& cv, Lock& lock, Pred pred) ->
bool
1267 cv.wait(lock, pred);
1281template <
unsigned IntervalMs = 10>
1284 template <
typename Lock,
typename Pred>
1285 static auto wait(std::condition_variable& cv, Lock& lock, Pred pred) ->
bool
1287 return cv.wait_for(lock, std::chrono::milliseconds(IntervalMs), pred);
1347template <
typename WaitPolicy>
1351 using Task = std::function<void()>;
1364 explicit ThreadPoolBase(
size_t num_threads = std::thread::hardware_concurrency(),
bool register_workers =
false)
1365 : num_threads_(num_threads == 0 ? 1 : num_threads), register_workers_(register_workers), stop_(false),
1366 start_time_(std::chrono::steady_clock::now())
1368 workers_.reserve(num_threads_);
1370 for (
size_t i = 0; i < num_threads_; ++i)
1372 workers_.emplace_back(&ThreadPoolBase::worker_function,
this, i);
1392 template <
typename F,
typename... Args>
1393 auto try_submit(F&& f, Args&&... args) ->
expected<std::future<std::invoke_result_t<F, Args...>>, std::error_code>
1395 using return_type = std::invoke_result_t<F, Args...>;
1397 auto task = std::make_shared<std::packaged_task<return_type()>>(
1400 std::future<return_type> result = task->get_future();
1403 std::lock_guard<std::mutex> lock(queue_mutex_);
1405 return unexpected(std::make_error_code(std::errc::operation_canceled));
1406 tasks_.emplace([task]() { (*task)(); });
1409 condition_.notify_one();
1417 template <
typename F,
typename... Args>
1418 auto submit(F&& f, Args&&... args) -> std::future<std::invoke_result_t<F, Args...>>
1420 auto result =
try_submit(std::forward<F>(f), std::forward<Args>(args)...);
1421 if (!result.has_value())
1422 throw std::runtime_error(
"Pool is shutting down");
1423 return std::move(result.value());
1434 template <
typename F,
typename... Args>
1437 auto r =
try_post(std::forward<F>(f), std::forward<Args>(args)...);
1439 throw std::runtime_error(
"Pool is shutting down");
1447 template <
typename F,
typename... Args>
1451 std::lock_guard<std::mutex> lock(queue_mutex_);
1453 return unexpected(std::make_error_code(std::errc::operation_canceled));
1454 tasks_.emplace(
detail::bind_args(std::forward<F>(f), std::forward<Args>(args)...));
1456 condition_.notify_one();
1460#if __cpp_lib_jthread >= 201911L
1467 template <
typename F,
typename... Args>
1468 auto submit(std::stop_token token, F&& f, Args&&... args) -> std::future<std::invoke_result_t<F, Args...>>
1470 return submit([token = std::move(token),
1471 bound =
detail::bind_args(std::forward<F>(f), std::forward<Args>(args)...)]()
mutable {
1472 if (token.stop_requested())
1473 return std::invoke_result_t<F, Args...>();
1479 template <
typename F,
typename... Args>
1480 auto try_submit(std::stop_token token, F&& f, Args&&... args)
1481 -> expected<std::future<std::invoke_result_t<F, Args...>>, std::error_code>
1483 return try_submit([token = std::move(token),
1484 bound = detail::bind_args(std::forward<F>(f), std::forward<Args>(args)...)]()
mutable {
1485 if (token.stop_requested())
1486 return std::invoke_result_t<F, Args...>();
1497 template <
typename Iterator>
1500 std::vector<std::future<void>> futures;
1501 futures.reserve(std::distance(begin, end));
1504 std::lock_guard<std::mutex> lock(queue_mutex_);
1506 return unexpected(std::make_error_code(std::errc::operation_canceled));
1508 for (
auto it = begin; it != end; ++it)
1510 auto task = std::make_shared<std::packaged_task<void()>>(*it);
1511 futures.push_back(task->get_future());
1512 tasks_.emplace([task]() { (*task)(); });
1516 condition_.notify_all();
1521 template <
typename Iterator>
1522 auto submit_batch(Iterator begin, Iterator end) -> std::vector<std::future<void>>
1525 if (!result.has_value())
1526 throw std::runtime_error(
"Pool is shutting down");
1527 return std::move(result.value());
1531 template <
typename Iterator,
typename F>
1537#if __cpp_lib_ranges >= 201911L
1539 template <std::ranges::input_range R>
1540 auto submit_batch(R&& range)
1542 return submit_batch(std::ranges::begin(range), std::ranges::end(range));
1545 template <std::ranges::input_range R>
1546 auto try_submit_batch(R&& range)
1548 return try_submit_batch(std::ranges::begin(range), std::ranges::end(range));
1551 template <std::ranges::input_range R,
typename F>
1554 parallel_for_each(std::ranges::begin(range), std::ranges::end(range), std::forward<F>(func));
1565 [[nodiscard]]
auto size() const noexcept ->
size_t
1567 return num_threads_;
1573 std::lock_guard<std::mutex> lock(queue_mutex_);
1574 return tasks_.size();
1612 std::unique_lock<std::mutex> lock(queue_mutex_);
1613 task_finished_condition_.wait(
1614 lock, [
this] {
return tasks_.empty() && active_tasks_.load(std::memory_order_acquire) == 0; });
1625 std::lock_guard<std::mutex> lock(queue_mutex_);
1631 std::queue<QueuedTask> empty;
1636 condition_.notify_all();
1638 for (
auto& worker : workers_)
1640 if (worker.joinable())
1655 auto const deadline = std::chrono::steady_clock::now() + timeout;
1658 std::lock_guard<std::mutex> lock(queue_mutex_);
1663 std::unique_lock<std::mutex> lock(queue_mutex_);
1664 bool const drained = task_finished_condition_.wait_until(
1665 lock, deadline, [
this] {
return tasks_.empty() && active_tasks_.load(std::memory_order_acquire) == 0; });
1680 auto const now = std::chrono::steady_clock::now();
1681 auto const elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - start_time_);
1683 std::lock_guard<std::mutex> lock(queue_mutex_);
1685 stats.total_threads = num_threads_;
1686 stats.active_threads = active_tasks_.load(std::memory_order_acquire);
1687 stats.pending_tasks = tasks_.size();
1688 stats.completed_tasks = completed_tasks_.load(std::memory_order_acquire);
1690 if (elapsed.count() > 0)
1692 stats.tasks_per_second =
static_cast<double>(stats.completed_tasks) / elapsed.count();
1696 stats.tasks_per_second = 0.0;
1699 auto const total_task_time = total_task_time_.load(std::memory_order_acquire);
1700 if (stats.completed_tasks > 0)
1702 stats.avg_task_time = std::chrono::microseconds(total_task_time / stats.completed_tasks);
1706 stats.avg_task_time = std::chrono::microseconds(0);
1723 std::lock_guard<std::mutex> lock(trace_mutex_);
1727 template <
typename Callback,
1728 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>,
TaskStartCallback>,
int> = 0>
1731 static_assert(std::is_invocable_r_v<void, Callback&, std::chrono::steady_clock::time_point, std::thread::id>,
1732 "Task start callback must accept (time_point, std::thread::id)");
1733 std::lock_guard<std::mutex> lock(trace_mutex_);
1735 std::forward<Callback>(cb));
1745 std::lock_guard<std::mutex> lock(trace_mutex_);
1749 template <
typename Callback,
1750 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>,
TaskEndCallback>,
int> = 0>
1754 std::is_invocable_r_v<void, Callback&, std::chrono::steady_clock::time_point, std::thread::id,
1755 std::chrono::microseconds>,
1756 "Task end callback must accept (time_point, std::thread::id, std::chrono::microseconds)");
1757 std::lock_guard<std::mutex> lock(trace_mutex_);
1760 std::chrono::microseconds)>(std::forward<Callback>(cb));
1766 size_t num_threads_;
1767 bool register_workers_;
1768 std::vector<ThreadWrapper> workers_;
1769 std::queue<QueuedTask> tasks_;
1771 mutable std::mutex queue_mutex_;
1772 std::condition_variable condition_;
1773 std::condition_variable task_finished_condition_;
1774 std::atomic<bool> stop_;
1775 std::atomic<size_t> active_tasks_{0};
1776 std::atomic<size_t> completed_tasks_{0};
1777 std::atomic<uint64_t> total_task_time_{0};
1779 std::mutex trace_mutex_;
1783 std::chrono::steady_clock::time_point start_time_;
1785 void worker_function(
size_t worker_id)
1787 std::optional<AutoRegisterCurrentThread> reg_guard;
1788 if (register_workers_)
1789 reg_guard.emplace(
"pool_worker_" + std::to_string(worker_id),
"threadschedule.pool");
1794 bool found_task =
false;
1797 std::unique_lock<std::mutex> lock(queue_mutex_);
1799 if (WaitPolicy::wait(condition_, lock, [
this] {
return stop_ || !tasks_.empty(); }))
1801 if (stop_ && tasks_.empty())
1806 if (!tasks_.empty())
1808 task = std::move(tasks_.front());
1811 active_tasks_.fetch_add(1, std::memory_order_relaxed);
1822 auto const start_time = std::chrono::steady_clock::now();
1823 auto const tid = std::this_thread::get_id();
1827 std::lock_guard<std::mutex> tl(trace_mutex_);
1828 on_task_start = on_task_start_;
1831 on_task_start(start_time, tid);
1841 auto const end_time = std::chrono::steady_clock::now();
1843 auto const task_duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
1844 total_task_time_.fetch_add(task_duration.count(), std::memory_order_relaxed);
1848 std::lock_guard<std::mutex> tl(trace_mutex_);
1849 on_task_end = on_task_end_;
1852 on_task_end(end_time, tid, task_duration);
1855 std::lock_guard<std::mutex> lock(queue_mutex_);
1856 active_tasks_.fetch_sub(1, std::memory_order_relaxed);
1858 completed_tasks_.fetch_add(1, std::memory_order_relaxed);
1860 task_finished_condition_.notify_all();
1961template <
size_t TaskSize = 64>
1971 : num_threads_(num_threads == 0 ? 1 : num_threads)
1973 workers_.reserve(num_threads_);
1974 for (
size_t i = 0; i < num_threads_; ++i)
1975 workers_.emplace_back(&LightweightPoolT::worker_loop,
this);
2000 template <
typename F,
typename... Args>
2003 auto r =
try_post(std::forward<F>(f), std::forward<Args>(args)...);
2005 throw std::runtime_error(
"LightweightPool is shutting down");
2014 template <
typename F,
typename... Args>
2019 std::lock_guard<std::mutex> lock(mutex_);
2021 return unexpected(std::make_error_code(std::errc::operation_canceled));
2022 tasks_.push(std::move(task));
2024 condition_.notify_one();
2037 template <
typename Iterator>
2042 throw std::runtime_error(
"LightweightPool is shutting down");
2049 template <
typename Iterator>
2053 std::lock_guard<std::mutex> lock(mutex_);
2055 return unexpected(std::make_error_code(std::errc::operation_canceled));
2056 for (
auto it = begin; it != end; ++it)
2059 condition_.notify_all();
2063#if __cpp_lib_ranges >= 201911L
2065 template <std::ranges::input_range R>
2066 void post_batch(R&& range)
2068 post_batch(std::ranges::begin(range), std::ranges::end(range));
2071 template <std::ranges::input_range R>
2072 auto try_post_batch(R&& range)
2074 return try_post_batch(std::ranges::begin(range), std::ranges::end(range));
2097 std::lock_guard<std::mutex> lock(mutex_);
2103 std::queue<detail::SboCallable<TaskSize>> empty;
2107 condition_.notify_all();
2108 for (
auto& w : workers_)
2127 auto const deadline = std::chrono::steady_clock::now() + timeout;
2129 std::lock_guard<std::mutex> lock(mutex_);
2133 std::unique_lock<std::mutex> lock(mutex_);
2134 bool const drained = drain_condition_.wait_until(
2135 lock, deadline, [
this] {
return tasks_.empty() && active_tasks_.load(std::memory_order_acquire) == 0; });
2147 [[nodiscard]]
auto size() const noexcept ->
size_t
2149 return num_threads_;
2183 size_t num_threads_;
2184 std::vector<ThreadWrapper> workers_;
2185 std::queue<detail::SboCallable<TaskSize>> tasks_;
2187 std::condition_variable condition_;
2188 std::condition_variable drain_condition_;
2189 std::atomic<bool> stop_{
false};
2190 std::atomic<size_t> active_tasks_{0};
2196 detail::SboCallable<TaskSize> task;
2198 std::unique_lock<std::mutex> lock(mutex_);
2199 condition_.wait(lock, [
this] {
return stop_ || !tasks_.empty(); });
2200 if (stop_ && tasks_.empty())
2202 if (!tasks_.empty())
2204 task = std::move(tasks_.front());
2206 active_tasks_.fetch_add(1, std::memory_order_relaxed);
2218 active_tasks_.fetch_sub(1, std::memory_order_relaxed);
2219 drain_condition_.notify_all();
2265template <
typename PoolType>
2275 static void init(
size_t num_threads)
2277 std::call_once(init_flag_(), [num_threads] { thread_count_() = num_threads; });
2283 static PoolType pool(thread_count_());
2291 template <
typename F,
typename... Args>
2294 return instance().submit(std::forward<F>(f), std::forward<Args>(args)...);
2297 template <
typename F,
typename... Args>
2300 return instance().try_submit(std::forward<F>(f), std::forward<Args>(args)...);
2303 template <
typename F,
typename... Args>
2304 static void post(F&& f, Args&&... args)
2306 instance().post(std::forward<F>(f), std::forward<Args>(args)...);
2309 template <
typename F,
typename... Args>
2312 return instance().try_post(std::forward<F>(f), std::forward<Args>(args)...);
2315 template <
typename Iterator>
2318 return instance().submit_batch(begin, end);
2321 template <
typename Iterator>
2324 return instance().try_submit_batch(begin, end);
2327 template <
typename Iterator,
typename F>
2330 instance().parallel_for_each(begin, end, std::forward<F>(func));
2333#if __cpp_lib_ranges >= 201911L
2334 template <std::ranges::input_range R>
2335 static auto submit_batch(R&& range)
2337 return instance().submit_batch(std::forward<R>(range));
2340 template <std::ranges::input_range R>
2341 static auto try_submit_batch(R&& range)
2343 return instance().try_submit_batch(std::forward<R>(range));
2346 template <std::ranges::input_range R,
typename F>
2349 instance().parallel_for_each(std::forward<R>(range), std::forward<F>(func));
2356 GlobalPool() =
default;
2358 static auto init_flag_() -> std::once_flag&
2360 static std::once_flag flag;
2364 static auto thread_count_() ->
size_t&
2366 static size_t count = std::thread::hardware_concurrency();
2400template <
typename Container,
typename F>
Feature-gated callable storage aliases for modern C++ builds.
Singleton accessor for a process-wide pool instance.
static auto submit_batch(Iterator begin, Iterator end)
static auto try_submit(F &&f, Args &&... args)
static auto try_post(F &&f, Args &&... args)
static void parallel_for_each(Iterator begin, Iterator end, F &&func)
static auto submit(F &&f, Args &&... args)
static void post(F &&f, Args &&... args)
static auto instance() -> PoolType &
Access the singleton pool instance (created on first call).
static auto try_submit_batch(Iterator begin, Iterator end)
static void init(size_t num_threads)
Pre-configure the number of threads before first use.
Ultra-lightweight fire-and-forget thread pool.
auto try_post_batch(Iterator begin, Iterator end) -> expected< void, std::error_code >
Batch post (non-throwing).
auto operator=(LightweightPoolT const &) -> LightweightPoolT &=delete
void post_batch(Iterator begin, Iterator end)
Post a range of callables under a single lock acquisition.
auto shutdown_for(std::chrono::milliseconds timeout) -> bool
Attempt a timed drain.
void shutdown(ShutdownPolicy policy=ShutdownPolicy::drain)
Shut the pool down.
LightweightPoolT(size_t num_threads=std::thread::hardware_concurrency())
Construct a lightweight pool with num_threads workers.
auto try_post(F &&f, Args &&... args) -> expected< void, std::error_code >
Post a fire-and-forget task (non-throwing variant).
auto set_affinity(ThreadAffinity const &affinity) -> expected< void, std::error_code >
Pin all workers to the same CPU set.
LightweightPoolT(LightweightPoolT const &)=delete
void post(F &&f, Args &&... args)
Post a fire-and-forget task (throwing variant).
auto size() const noexcept -> size_t
Number of worker threads.
auto configure_threads(std::string const &name_prefix, SchedulingPolicy policy=SchedulingPolicy::OTHER, ThreadPriority priority=ThreadPriority::normal()) -> expected< void, std::error_code >
Name, schedule and prioritize all worker threads.
auto distribute_across_cpus() -> expected< void, std::error_code >
Pin each worker to a distinct CPU core (round-robin).
Manages a set of CPU indices to which a thread may be bound.
Single-queue thread pool parameterized by its idle-wait strategy.
void parallel_for_each(Iterator begin, Iterator end, F &&func)
Apply func to [begin, end) in parallel (chunked).
auto set_affinity(ThreadAffinity const &affinity) -> expected< void, std::error_code >
Pin all workers to the same CPU set.
void set_on_task_start(TaskStartCallback cb)
Register a callback invoked just before each task executes.
void set_on_task_start(Callback &&cb)
auto shutdown_for(std::chrono::milliseconds timeout) -> bool
Attempt a timed drain: finish as many tasks as possible within timeout, then force-stop remaining wor...
auto operator=(ThreadPoolBase const &) -> ThreadPoolBase &=delete
auto try_submit(F &&f, Args &&... args) -> expected< std::future< std::invoke_result_t< F, Args... > >, std::error_code >
Submit a task without throwing on shutdown.
void wait_for_tasks()
Block until all pending and active tasks have completed.
auto distribute_across_cpus() -> expected< void, std::error_code >
Pin each worker to a distinct CPU core (round-robin).
void shutdown(ShutdownPolicy policy=ShutdownPolicy::drain)
detail::move_callable< void()> QueuedTask
ThreadPoolBase(ThreadPoolBase const &)=delete
auto try_post(F &&f, Args &&... args) -> expected< void, std::error_code >
void post(F &&f, Args &&... args)
Fire-and-forget task submission (throwing variant).
auto configure_threads(std::string const &name_prefix, SchedulingPolicy policy=SchedulingPolicy::OTHER, ThreadPriority priority=ThreadPriority::normal()) -> expected< void, std::error_code >
Name, schedule and prioritize all worker threads.
std::function< void()> Task
auto submit_batch(Iterator begin, Iterator end) -> std::vector< std::future< void > >
Submit a batch of tasks (throwing).
void set_on_task_end(TaskEndCallback cb)
Register a callback invoked just after each task completes.
auto pending_tasks() const -> size_t
Number of tasks waiting in the queue.
auto size() const noexcept -> size_t
Number of worker threads.
auto submit(F &&f, Args &&... args) -> std::future< std::invoke_result_t< F, Args... > >
Submit a task, throwing on shutdown.
ThreadPoolBase(size_t num_threads=std::thread::hardware_concurrency(), bool register_workers=false)
auto get_statistics() const -> Statistics
Collect approximate performance counters.
void set_on_task_end(Callback &&cb)
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).
Value-semantic wrapper for a thread scheduling priority.
static constexpr auto normal() noexcept -> ThreadPriority
auto clear_and_count() -> size_t
auto steal(T &item) -> bool
static constexpr size_t DEFAULT_CAPACITY
auto pop(T &item) -> bool
auto size() const -> size_t
auto push(T const &item) -> bool
static constexpr size_t CACHE_LINE_SIZE
auto push(T &&item) -> bool
auto empty() const -> bool
WorkStealingDeque(size_t capacity=DEFAULT_CAPACITY)
Type-erased, move-only callable with configurable inline storage.
auto operator=(SboCallable const &) -> SboCallable &=delete
SboCallable(SboCallable &&other) noexcept
auto operator=(SboCallable &&other) noexcept -> SboCallable &
SboCallable(SboCallable const &)=delete
A result type that holds either a value of type T or an error of type E.
Exception thrown by expected::value() when the object is in the error state.
Polyfill for std::expected (C++23) for pre-C++23 compilers.
std::function< Signature > copyable_callable
auto bind_args(F &&f, Args &&... args)
Bind a callable with its arguments into a nullary lambda.
auto distribute_workers_across_cpus(WorkerRange &workers) -> expected< void, std::error_code >
auto make_move_callable(Callable &&callable) -> move_callable< Signature >
std::function< Signature > move_callable
auto configure_worker_threads(WorkerRange &workers, std::string const &name_prefix, SchedulingPolicy policy, ThreadPriority priority) -> expected< void, std::error_code >
auto make_copyable_callable(Callable &&callable) -> copyable_callable< Signature >
auto set_worker_affinity(WorkerRange &workers, ThreadAffinity const &affinity) -> expected< void, std::error_code >
void parallel_for_each_chunked(Pool &pool, Iterator begin, Iterator end, F &&func, size_t num_workers)
SchedulingPolicy
Enumeration of available thread scheduling policies.
@ OTHER
Standard round-robin time-sharing.
ShutdownPolicy
Controls how a pool handles pending tasks during shutdown.
@ drop_pending
Finish running tasks, discard queued ones.
@ drain
Finish all queued tasks before stopping (default).
ThreadPoolBase< IndefiniteWait > ThreadPool
General-purpose thread pool with indefinite blocking wait.
void parallel_for_each(Container &container, F &&func)
Convenience wrapper that applies a callable to every element of a container in parallel using the Glo...
GlobalPool< ThreadPool > GlobalThreadPool
Singleton accessor for the process-wide ThreadPool instance.
TaskEndCallback TaskEndCallbackStorage
ThreadPoolBase< PollingWait<> > FastThreadPool
Thread pool with 10 ms polling wait for lower wake-up latency.
GlobalPool< HighPerformancePool > GlobalHighPerformancePool
Singleton accessor for the process-wide HighPerformancePool instance.
detail::copyable_callable< void(std::chrono::steady_clock::time_point, std::thread::id)> TaskStartCallback
Work-stealing deque for per-thread task queues in a thread pool.
detail::copyable_callable< void(std::chrono::steady_clock::time_point, std::thread::id, std::chrono::microseconds elapsed)> TaskEndCallback
Callback invoked when a pool worker finishes executing a task.
TaskStartCallback TaskStartCallbackStorage
LightweightPoolT<> LightweightPool
Default lightweight pool with 64-byte task slots (56 bytes usable).
Scheduling policies, thread priority, and CPU affinity types.
Wait policy that blocks indefinitely until work is available.
static auto wait(std::condition_variable &cv, Lock &lock, Pred pred) -> bool
Wait policy that polls with a configurable timeout.
static auto wait(std::condition_variable &cv, Lock &lock, Pred pred) -> bool
std::chrono::microseconds avg_task_time
Process-wide thread registry, control blocks, and composite registry.
Enhanced thread wrappers: ThreadWrapper, JThreadWrapper, and non-owning views.