10#include "../callable/bind.hpp"
11#include "../callable/move_only_function.hpp"
20#include <condition_variable>
29#include <system_error>
112template <
size_t TaskSize = 64>
115 static_assert(TaskSize >= 2 *
sizeof(
void*),
"TaskSize must hold the operation pointer and heap fallback pointer");
116 static_assert(TaskSize %
alignof(
void*) == 0,
"TaskSize must be a multiple of pointer alignment");
119 static_assert(
sizeof(
queued_task) == TaskSize,
"TaskSize must equal the complete callable storage size");
132 workers_.reserve(num_threads_);
135 for (
size_t i = 0; i < num_threads_; ++i)
136 workers_.emplace_back(&lightweight_pool_backend_base::worker_loop,
this, i);
137 startup_.
wait(num_threads_);
141 stop_.store(
true, std::memory_order_release);
142 condition_.notify_all();
143 for (
auto& worker : workers_)
144 if (worker.joinable())
172 template <
typename F,
typename... Args>
176 auto r =
try_post(std::forward<F>(f), std::forward<Args>(args)...);
178 throw std::runtime_error(
"lightweight_pool_backend is shutting down");
187 template <
typename F,
typename... Args>
193 std::lock_guard<std::mutex> lock(mutex_);
195 return unexpected(std::make_error_code(std::errc::operation_canceled));
196 tasks_.push(std::move(task));
198 condition_.notify_one();
212 template <
typename Iterator>
218 throw std::runtime_error(
"lightweight_pool_backend is shutting down");
225 template <
typename Iterator>
229 std::vector<queued_task> prepared;
231 for (
auto it = begin; it != end; ++it)
232 prepared.emplace_back(*it);
234 bool enqueued =
false;
237 std::lock_guard<std::mutex> lock(mutex_);
239 return unexpected(std::make_error_code(std::errc::operation_canceled));
240 for (
auto& task : prepared)
242 tasks_.push(std::move(task));
249 condition_.notify_all();
252 condition_.notify_all();
276 std::lock_guard<std::recursive_timed_mutex> shutdown_lock(shutdown_mutex_);
277 if (workers_.empty())
279 std::queue<queued_task> discarded;
281 std::lock_guard<std::mutex> lock(mutex_);
285 tasks_.swap(discarded);
287 shutdown_completed_all_ = shutdown_completed_all_ && discarded.empty();
288 condition_.notify_all();
289 drain_condition_.notify_all();
291 std::queue<queued_task> empty;
292 discarded.swap(empty);
294 for (
auto& w : workers_)
300 shutdown_completed_at_ = std::chrono::steady_clock::now();
320 std::unique_lock<std::recursive_timed_mutex> shutdown_lock(shutdown_mutex_, std::defer_lock);
321 if (deadline == std::chrono::steady_clock::time_point::max())
322 shutdown_lock.lock();
323 else if (!shutdown_lock.try_lock_until(deadline))
325 std::unique_lock<std::mutex> lock(mutex_);
326 if (workers_.empty())
327 return shutdown_completed_all_ && shutdown_completed_at_ <= deadline;
330 condition_.notify_all();
331 bool const drained = drain_condition_.wait_until(
332 lock, deadline, [
this] {
return tasks_.empty() && active_tasks_.load(std::memory_order_acquire) == 0; });
333 std::queue<queued_task> discarded;
335 tasks_.swap(discarded);
336 shutdown_completed_all_ = shutdown_completed_all_ && discarded.empty();
338 condition_.notify_all();
339 drain_condition_.notify_all();
341 std::queue<queued_task> empty;
342 discarded.swap(empty);
344 if (deadline == std::chrono::steady_clock::time_point::max())
346 for (
auto& worker : workers_)
347 if (worker.joinable())
351 if (drained && shutdown_completed_at_ == std::chrono::steady_clock::time_point{})
352 shutdown_completed_at_ = std::chrono::steady_clock::now();
353 return drained && shutdown_completed_all_;
363 size() const noexcept ->
size_t
371 return current_pool ==
this;
417 bool register_workers_;
419 std::vector<detail::thread_backend> workers_;
420 std::queue<queued_task> tasks_;
422 std::condition_variable condition_;
423 std::condition_variable drain_condition_;
424 std::recursive_timed_mutex shutdown_mutex_;
425 std::atomic<bool> stop_{
false };
426 bool shutdown_completed_all_{
true };
427 std::chrono::steady_clock::time_point shutdown_completed_at_{};
428 std::atomic<size_t> active_tasks_{ 0 };
432 worker_loop(
size_t worker_id)
434 detail::worker_context_guard<lightweight_pool_backend_base> worker_context(current_pool,
this);
435 std::optional<registration_guard_backend> registration;
438 if (register_workers_)
439 registration.emplace(
"light_worker_" + std::to_string(worker_id),
"threadschedule.pool");
444 startup_.
arrive(std::current_exception());
451 std::unique_lock<std::mutex> lock(mutex_);
452 condition_.wait(lock, [
this] {
return stop_ || !tasks_.empty(); });
453 if (stop_ && tasks_.empty())
457 task = std::move(tasks_.front());
459 active_tasks_.fetch_add(1, std::memory_order_relaxed);
472 std::lock_guard<std::mutex> lock(mutex_);
473 active_tasks_.fetch_sub(1, std::memory_order_relaxed);
475 drain_condition_.notify_all();
Ultra-lightweight fire-and-forget thread pool.
void post_batch(Iterator begin, Iterator end)
Post a range of callables under a single lock acquisition.
void post(F &&f, Args &&... args)
Post a fire-and-forget task (throwing variant).
auto configure_threads(native_thread_config const &config) -> expected< void, std::error_code >
lightweight_pool_backend_base(size_t num_threads=default_worker_count(), bool register_workers=false)
Construct a lightweight pool with num_threads workers.
void shutdown(shutdown_policy_backend policy=shutdown_policy_backend::drain)
Shut the pool down.
~lightweight_pool_backend_base()
auto try_post(F &&f, Args &&... args) -> expected< void, std::error_code >
Post a fire-and-forget task (non-throwing variant).
auto set_affinity(native_thread_affinity const &affinity) -> expected< void, std::error_code >
Pin all workers to the same CPU set.
auto size() const noexcept -> size_t
Number of worker threads.
auto operator=(lightweight_pool_backend_base const &) -> lightweight_pool_backend_base &=delete
auto distribute_across_cpus() -> expected< void, std::error_code >
Pin each worker to a distinct CPU core (round-robin).
auto is_current_worker() const noexcept -> bool
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.
lightweight_pool_backend_base(lightweight_pool_backend_base const &)=delete
auto try_post_batch(Iterator begin, Iterator end) -> expected< void, std::error_code >
Batch post (non-throwing).
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
void arrive(std::exception_ptr error={})
void wait(size_t expected)
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 >
auto multipass_range_size(Iterator begin, Iterator end) -> size_t
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 set_worker_affinity(WorkerRange &workers, native_thread_affinity const &affinity) -> expected< void, std::error_code >
auto runtime_registry() -> thread_registry_backend &
auto shutdown_deadline_after(std::chrono::milliseconds timeout) -> std::chrono::steady_clock::time_point
Internal queued-task shutdown behavior.
Worker identity, CPU selection, and registration helpers.
Worker-thread count configuration for pool types.