10#include "../../expected.hpp"
11#include "../registry/backend.hpp"
12#include "../scheduling/native.hpp"
13#include "../thread_backend.hpp"
16#include <condition_variable>
23#include <system_error>
35 arrive(std::exception_ptr error = {})
38 std::lock_guard<std::mutex> lock(mutex_);
40 error_ = std::move(error);
43 condition_.notify_one();
49 std::unique_lock<std::mutex> lock(mutex_);
50 condition_.wait(lock, [&] {
return arrived_ ==
expected; });
52 std::rethrow_exception(error_);
57 std::condition_variable condition_;
59 std::exception_ptr error_;
62template <
typename Iterator>
64 = std::is_base_of_v<std::forward_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category>;
66template <
typename Iterator>
67[[nodiscard]]
inline auto
70 if constexpr (is_forward_iterator_v<Iterator>)
71 return static_cast<size_t>(std::distance(begin, end));
75template <
typename Pool>
97[[noreturn]]
inline void
100 throw std::system_error(std::make_error_code(std::errc::resource_deadlock_would_occur),
101 "pool lifecycle operation called from its own worker");
107 std::string
const suffix =
"_" + std::to_string(index);
109 constexpr size_t linux_name_limit = 15;
110 if (suffix.size() >= linux_name_limit)
111 return suffix.substr(suffix.size() - linux_name_limit);
112 return name_prefix.substr(0, linux_name_limit - suffix.size()) + suffix;
114 return name_prefix + suffix;
118template <
typename WorkerRange>
124 std::error_code first_error;
125 for (
size_t i = 0; i < workers.size(); ++i)
128 auto named = workers[i].set_name(thread_name);
129 if (!named && !first_error)
130 first_error = named.error();
131 if (named && registry !=
nullptr)
132 registry->update_registered_name(workers[i].native_id(), thread_name);
133 auto scheduled = workers[i].set_scheduling_policy(policy, priority);
134 if (!scheduled && !first_error)
135 first_error = scheduled.error();
142template <
typename WorkerRange>
147 std::error_code first_error;
148 for (
size_t i = 0; i < workers.size(); ++i)
153 auto named = workers[i].set_name(thread_name);
154 if (!named && !first_error)
155 first_error = named.error();
156 if (named && registry !=
nullptr)
157 registry->update_registered_name(workers[i].native_id(), thread_name);
159 if (config.scheduling)
161 auto scheduled = workers[i].configure(*config.scheduling);
162 if (!scheduled && !first_error)
163 first_error = scheduled.error();
165 if (config.affinity.has_value())
167 auto affinity = workers[i].set_affinity(*config.affinity);
168 if (!affinity && !first_error)
169 first_error = affinity.error();
177template <
typename WorkerRange>
181 std::error_code first_error;
182 for (
auto& worker : workers)
184 auto configured = worker.set_affinity(affinity);
185 if (!configured && !first_error)
186 first_error = configured.
error();
193template <
typename WorkerRange>
201 auto const cpus = allowed->get_cpus();
203 return unexpected(std::make_error_code(std::errc::invalid_argument));
205 std::error_code first_error;
206 for (
size_t i = 0; i < workers.size(); ++i)
209 auto configured = workers[i].set_affinity(affinity);
210 if (!configured && !first_error)
211 first_error = configured.error();
218template <
typename Pool,
typename Iterator,
typename F>
222 static_assert(is_forward_iterator_v<Iterator>,
"parallel_for_each requires at least a forward iterator");
223 auto const total =
static_cast<size_t>(std::distance(begin, end));
227 size_t const chunk_size = (std::max)(
size_t(1), total / (num_workers * 4));
228 std::vector<std::future<void>> futures;
230 std::exception_ptr first_error;
236 auto remaining =
static_cast<size_t>(std::distance(it, end));
237 auto this_chunk = (std::min)(chunk_size, remaining);
239 std::advance(chunk_end, this_chunk);
241 auto submitted = pool.try_submit(
242 [it, chunk_end, &func]()
244 for (
auto cur = it; cur != chunk_end; ++cur)
248 throw std::system_error(submitted.error(),
"parallel_for_each submission failed");
249 futures.push_back(std::move(submitted.value()));
256 first_error = std::current_exception();
259 for (
auto& f : futures)
268 first_error = std::current_exception();
273 std::rethrow_exception(first_error);
Manages a set of CPU indices to which a thread may be bound.
Value-semantic wrapper for a thread scheduling priority.
Lightweight handle for querying and controlling a specific OS thread.
auto get_affinity() const -> expected< native_thread_affinity, std::error_code >
Central registry of threads indexed by OS-level thread ID (native_thread_id).
worker_context_guard(worker_context_guard const &)=delete
worker_context_guard(Pool *&slot, Pool *current) noexcept
auto operator=(worker_context_guard const &) -> worker_context_guard &=delete
void arrive(std::exception_ptr error={})
void wait(size_t expected)
constexpr auto error() &noexcept -> E &
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.
auto distribute_workers_across_cpus(WorkerRange &workers) -> expected< void, std::error_code >
constexpr bool is_forward_iterator_v
auto multipass_range_size(Iterator begin, Iterator end) -> size_t
void throw_worker_deadlock()
auto set_worker_affinity(WorkerRange &workers, native_thread_affinity const &affinity) -> expected< void, std::error_code >
auto worker_thread_name(std::string const &name_prefix, size_t index) -> std::string
void parallel_for_each_chunked(Pool &pool, Iterator begin, Iterator end, F &&func, size_t num_workers)