ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
worker_context_guard.hpp
Go to the documentation of this file.
1#pragma once
2
10#include "../../expected.hpp"
11#include "../registry/backend.hpp"
12#include "../scheduling/native.hpp"
13#include "../thread_backend.hpp"
14
15#include <algorithm>
16#include <condition_variable>
17#include <cstddef>
18#include <exception>
19#include <future>
20#include <iterator>
21#include <mutex>
22#include <string>
23#include <system_error>
24#include <type_traits>
25#include <utility>
26#include <vector>
27
29{
30
32{
33public:
34 void
35 arrive(std::exception_ptr error = {})
36 {
37 {
38 std::lock_guard<std::mutex> lock(mutex_);
39 if (error && !error_)
40 error_ = std::move(error);
41 ++arrived_;
42 }
43 condition_.notify_one();
44 }
45
46 void
47 wait(size_t expected)
48 {
49 std::unique_lock<std::mutex> lock(mutex_);
50 condition_.wait(lock, [&] { return arrived_ == expected; });
51 if (error_)
52 std::rethrow_exception(error_);
53 }
54
55private:
56 std::mutex mutex_;
57 std::condition_variable condition_;
58 size_t arrived_{ 0 };
59 std::exception_ptr error_;
60};
61
62template <typename Iterator>
63inline constexpr bool is_forward_iterator_v
64 = std::is_base_of_v<std::forward_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category>;
65
66template <typename Iterator>
67[[nodiscard]] inline auto
68multipass_range_size(Iterator begin, Iterator end) -> size_t
69{
70 if constexpr (is_forward_iterator_v<Iterator>)
71 return static_cast<size_t>(std::distance(begin, end));
72 return 0;
73}
74
75template <typename Pool>
77{
78public:
79 worker_context_guard(Pool*& slot, Pool* current) noexcept : slot_(slot), previous_(slot)
80 {
81 slot_ = current;
82 }
83
85 {
86 slot_ = previous_;
87 }
88
91
92private:
93 Pool*& slot_;
94 Pool* previous_;
95};
96
97[[noreturn]] inline void
99{
100 throw std::system_error(std::make_error_code(std::errc::resource_deadlock_would_occur),
101 "pool lifecycle operation called from its own worker");
102}
103
104inline auto
105worker_thread_name(std::string const& name_prefix, size_t index) -> std::string
106{
107 std::string const suffix = "_" + std::to_string(index);
108#ifndef _WIN32
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;
113#else
114 return name_prefix + suffix;
115#endif
116}
117
118template <typename WorkerRange>
119inline auto
120configure_worker_threads(WorkerRange& workers, std::string const& name_prefix, native_scheduling_policy policy,
121 native_thread_priority priority, thread_registry_backend* registry = nullptr)
123{
124 std::error_code first_error;
125 for (size_t i = 0; i < workers.size(); ++i)
126 {
127 std::string const thread_name = worker_thread_name(name_prefix, 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();
136 }
137 if (first_error)
138 return unexpected(first_error);
139 return {};
140}
141
142template <typename WorkerRange>
143inline auto
144configure_worker_threads(WorkerRange& workers, native_thread_config const& config,
146{
147 std::error_code first_error;
148 for (size_t i = 0; i < workers.size(); ++i)
149 {
150 if (config.name)
151 {
152 std::string const thread_name = worker_thread_name(*config.name, 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);
158 }
159 if (config.scheduling)
160 {
161 auto scheduled = workers[i].configure(*config.scheduling);
162 if (!scheduled && !first_error)
163 first_error = scheduled.error();
164 }
165 if (config.affinity.has_value())
166 {
167 auto affinity = workers[i].set_affinity(*config.affinity);
168 if (!affinity && !first_error)
169 first_error = affinity.error();
170 }
171 }
172 if (first_error)
173 return unexpected(first_error);
174 return {};
175}
176
177template <typename WorkerRange>
178inline auto
180{
181 std::error_code first_error;
182 for (auto& worker : workers)
183 {
184 auto configured = worker.set_affinity(affinity);
185 if (!configured && !first_error)
186 first_error = configured.error();
187 }
188 if (first_error)
189 return unexpected(first_error);
190 return {};
191}
192
193template <typename WorkerRange>
194inline auto
196{
197 auto allowed = thread_info().get_affinity();
198 if (!allowed)
199 return unexpected(allowed.error());
200
201 auto const cpus = allowed->get_cpus();
202 if (cpus.empty())
203 return unexpected(std::make_error_code(std::errc::invalid_argument));
204
205 std::error_code first_error;
206 for (size_t i = 0; i < workers.size(); ++i)
207 {
208 native_thread_affinity affinity({ cpus[i % cpus.size()] });
209 auto configured = workers[i].set_affinity(affinity);
210 if (!configured && !first_error)
211 first_error = configured.error();
212 }
213 if (first_error)
214 return unexpected(first_error);
215 return {};
216}
217
218template <typename Pool, typename Iterator, typename F>
219inline void
220parallel_for_each_chunked(Pool& pool, Iterator begin, Iterator end, F&& func, size_t num_workers)
221{
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));
224 if (total == 0)
225 return;
226
227 size_t const chunk_size = (std::max)(size_t(1), total / (num_workers * 4));
228 std::vector<std::future<void>> futures;
229 auto it = begin;
230 std::exception_ptr first_error;
231
232 try
233 {
234 while (it != end)
235 {
236 auto remaining = static_cast<size_t>(std::distance(it, end));
237 auto this_chunk = (std::min)(chunk_size, remaining);
238 auto chunk_end = it;
239 std::advance(chunk_end, this_chunk);
240
241 auto submitted = pool.try_submit(
242 [it, chunk_end, &func]()
243 {
244 for (auto cur = it; cur != chunk_end; ++cur)
245 func(*cur);
246 });
247 if (!submitted)
248 throw std::system_error(submitted.error(), "parallel_for_each submission failed");
249 futures.push_back(std::move(submitted.value()));
250
251 it = chunk_end;
252 }
253 }
254 catch (...)
255 {
256 first_error = std::current_exception();
257 }
258
259 for (auto& f : futures)
260 {
261 try
262 {
263 f.get();
264 }
265 catch (...)
266 {
267 if (!first_error)
268 first_error = std::current_exception();
269 }
270 }
271
272 if (first_error)
273 std::rethrow_exception(first_error);
274}
275
276} // 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
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
constexpr auto error() &noexcept -> E &
Definition expected.hpp:668
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
auto distribute_workers_across_cpus(WorkerRange &workers) -> expected< void, std::error_code >
auto multipass_range_size(Iterator begin, Iterator end) -> size_t
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)