ThreadSchedule 1.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
scheduler_policy.hpp
1#pragma once
2
3#include "expected.hpp"
4#include <algorithm>
5#include <cstdint>
6#include <sstream>
7#include <string>
8#include <system_error>
9#include <vector>
10
11#ifdef _WIN32
12#include <windows.h>
13#else
14#include <sched.h>
15#include <sys/resource.h>
16#endif
17
18namespace threadschedule
19{
20// expected/result are provided by expected.hpp
21
54enum class SchedulingPolicy : std::uint_fast8_t
55{
56#ifdef _WIN32
57 // Windows doesn't have the same scheduling policies as Linux
58 // We'll use generic values
59 OTHER = 0,
60 FIFO = 1,
61 RR = 2,
62 BATCH = 3,
63 IDLE = 4
64#else
65 OTHER = SCHED_OTHER,
66 FIFO = SCHED_FIFO,
67 RR = SCHED_RR,
68 BATCH = SCHED_BATCH,
69 IDLE = SCHED_IDLE,
70#ifdef SCHED_DEADLINE
71 DEADLINE = SCHED_DEADLINE
72#endif
73#endif
74};
75
106class ThreadPriority
107{
108 public:
109 constexpr explicit ThreadPriority(int priority = 0) : priority_(std::clamp(priority, min_priority, max_priority))
110 {
111 }
112
113 [[nodiscard]] constexpr auto value() const noexcept -> int
114 {
115 return priority_;
116 }
117 [[nodiscard]] constexpr auto is_valid() const noexcept -> bool
118 {
119 return priority_ >= min_priority && priority_ <= max_priority;
120 }
121
122 [[nodiscard]] static constexpr auto lowest() noexcept -> ThreadPriority
123 {
124 return ThreadPriority(min_priority);
125 }
126 [[nodiscard]] static constexpr auto normal() noexcept -> ThreadPriority
127 {
128 return ThreadPriority(0);
129 }
130 [[nodiscard]] static constexpr auto highest() noexcept -> ThreadPriority
131 {
132 return ThreadPriority(max_priority);
133 }
134
135 [[nodiscard]] constexpr auto operator==(ThreadPriority const& other) const noexcept -> bool
136 {
137 return priority_ == other.priority_;
138 }
139 [[nodiscard]] constexpr auto operator!=(ThreadPriority const& other) const noexcept -> bool
140 {
141 return priority_ != other.priority_;
142 }
143 [[nodiscard]] constexpr auto operator<(ThreadPriority const& other) const noexcept -> bool
144 {
145 return priority_ < other.priority_;
146 }
147 [[nodiscard]] constexpr auto operator<=(ThreadPriority const& other) const noexcept -> bool
148 {
149 return priority_ <= other.priority_;
150 }
151 [[nodiscard]] constexpr auto operator>(ThreadPriority const& other) const noexcept -> bool
152 {
153 return priority_ > other.priority_;
154 }
155 [[nodiscard]] constexpr auto operator>=(ThreadPriority const& other) const noexcept -> bool
156 {
157 return priority_ >= other.priority_;
158 }
159
160 [[nodiscard]] auto to_string() const -> std::string
161 {
162 std::ostringstream oss;
163 oss << "ThreadPriority(" << priority_ << ")";
164 return oss.str();
165 }
166
167 private:
168 static constexpr int min_priority = -20;
169 static constexpr int max_priority = 19;
170 int priority_;
171};
172
206class ThreadAffinity
207{
208 public:
209 ThreadAffinity()
210 {
211#ifdef _WIN32
212 group_ = 0;
213 mask_ = 0;
214#else
215 CPU_ZERO(&cpuset_);
216#endif
217 }
218
219 explicit ThreadAffinity(std::vector<int> const& cpus) : ThreadAffinity()
220 {
221 for (int cpu : cpus)
222 {
223 add_cpu(cpu);
224 }
225 }
226
227 // Adds a CPU index. On Windows, indices >= 64 select group = cpu/64 automatically.
228 void add_cpu(int cpu)
229 {
230#ifdef _WIN32
231 if (cpu < 0)
232 return;
233 WORD g = static_cast<WORD>(cpu / 64);
234 int bit = cpu % 64;
235 if (!has_any())
236 {
237 group_ = g;
238 }
239 if (g != group_)
240 {
241 // Single-group affinity object: ignore CPUs from other groups
242 return;
243 }
244 mask_ |= (static_cast<unsigned long long>(1) << bit);
245#else
246 if (cpu >= 0 && cpu < CPU_SETSIZE)
247 {
248 CPU_SET(cpu, &cpuset_);
249 }
250#endif
251 }
252
253 void remove_cpu(int cpu)
254 {
255#ifdef _WIN32
256 if (cpu < 0)
257 return;
258 WORD g = static_cast<WORD>(cpu / 64);
259 int bit = cpu % 64;
260 if (g == group_)
261 {
262 mask_ &= ~(static_cast<unsigned long long>(1) << bit);
263 }
264#else
265 if (cpu >= 0 && cpu < CPU_SETSIZE)
266 {
267 CPU_CLR(cpu, &cpuset_);
268 }
269#endif
270 }
271
272 [[nodiscard]] auto is_set(int cpu) const -> bool
273 {
274#ifdef _WIN32
275 if (cpu < 0)
276 return false;
277 WORD g = static_cast<WORD>(cpu / 64);
278 int bit = cpu % 64;
279 return g == group_ && (mask_ & (static_cast<unsigned long long>(1) << bit)) != 0;
280#else
281 return cpu >= 0 && cpu < CPU_SETSIZE && CPU_ISSET(cpu, &cpuset_);
282#endif
283 }
284
285 [[nodiscard]] auto has_cpu(int cpu) const -> bool
286 {
287 return is_set(cpu);
288 }
289
290 void clear()
291 {
292#ifdef _WIN32
293 mask_ = 0;
294#else
295 CPU_ZERO(&cpuset_);
296#endif
297 }
298
299 [[nodiscard]] auto get_cpus() const -> std::vector<int>
300 {
301 std::vector<int> cpus;
302#ifdef _WIN32
303 for (int i = 0; i < 64; ++i)
304 {
305 if (mask_ & (static_cast<unsigned long long>(1) << i))
306 {
307 cpus.push_back(static_cast<int>(group_) * 64 + i);
308 }
309 }
310#else
311 for (int i = 0; i < CPU_SETSIZE; ++i)
312 {
313 if (CPU_ISSET(i, &cpuset_))
314 {
315 cpus.push_back(i);
316 }
317 }
318#endif
319 return cpus;
320 }
321
322#ifdef _WIN32
323 [[nodiscard]] unsigned long long get_mask() const
324 {
325 return mask_;
326 }
327 [[nodiscard]] WORD get_group() const
328 {
329 return group_;
330 }
331 [[nodiscard]] bool has_any() const
332 {
333 return mask_ != 0;
334 }
335#else
336 [[nodiscard]] auto native_handle() const -> cpu_set_t const&
337 {
338 return cpuset_;
339 }
340#endif
341
342 [[nodiscard]] auto to_string() const -> std::string
343 {
344 auto cpus = get_cpus();
345 std::ostringstream oss;
346 oss << "ThreadAffinity({";
347 for (size_t i = 0; i < cpus.size(); ++i)
348 {
349 if (i > 0)
350 oss << ", ";
351 oss << cpus[i];
352 }
353 oss << "})";
354 return oss.str();
355 }
356
357 private:
358#ifdef _WIN32
359 WORD group_;
360 unsigned long long mask_;
361#else
362 cpu_set_t cpuset_;
363#endif
364};
365
401{
402 public:
403#ifdef _WIN32
404 // Windows doesn't use sched_param, but we'll define a compatible type
405 struct sched_param_win
406 {
407 int sched_priority;
408 };
409
410 static expected<sched_param_win, std::error_code> create_for_policy(SchedulingPolicy policy,
411 ThreadPriority priority)
412 {
413 sched_param_win param{};
414 // On Windows, priority is directly used
415 param.sched_priority = priority.value();
416 return param;
417 }
418
419 static expected<int, std::error_code> get_priority_range(SchedulingPolicy policy)
420 {
421 // Windows thread priorities range from -15 to +15
422 return 30;
423 }
424#else
425 static auto create_for_policy(SchedulingPolicy policy, ThreadPriority priority)
427 {
428 sched_param param{};
429
430 int const policy_int = static_cast<int>(policy);
431 int const min_prio = sched_get_priority_min(policy_int);
432 int const max_prio = sched_get_priority_max(policy_int);
433
434 if (min_prio == -1 || max_prio == -1)
435 {
436 return unexpected(std::make_error_code(std::errc::invalid_argument));
437 }
438
439 param.sched_priority = std::clamp(priority.value(), min_prio, max_prio);
440 return param;
441 }
442
443 static auto get_priority_range(SchedulingPolicy policy) -> expected<int, std::error_code>
444 {
445 int const policy_int = static_cast<int>(policy);
446 int const min_prio = sched_get_priority_min(policy_int);
447 int const max_prio = sched_get_priority_max(policy_int);
448
449 if (min_prio == -1 || max_prio == -1)
450 {
451 return unexpected(std::make_error_code(std::errc::invalid_argument));
452 }
453
454 return max_prio - min_prio;
455 }
456#endif
457};
458
462inline auto to_string(SchedulingPolicy policy) -> std::string
463{
464 switch (policy)
465 {
466 case SchedulingPolicy::OTHER:
467 return "OTHER";
468 case SchedulingPolicy::FIFO:
469 return "FIFO";
470 case SchedulingPolicy::RR:
471 return "RR";
472 case SchedulingPolicy::BATCH:
473 return "BATCH";
474 case SchedulingPolicy::IDLE:
475 return "IDLE";
476#if defined(SCHED_DEADLINE) && !defined(_WIN32)
477 case SchedulingPolicy::DEADLINE:
478 return "DEADLINE";
479#endif
480 default:
481 return "UNKNOWN";
482 }
483}
484
485} // namespace threadschedule
Static utility class for constructing OS-native scheduling parameters.
Value-semantic wrapper for a thread scheduling priority.
A result type that holds either a value of type T or an error of type E.
Definition expected.hpp:215
Exception thrown by expected::value() when the object is in the error state.
Definition expected.hpp:162
Polyfill for std::expected (C++23) for pre-C++23 compilers.