ThreadSchedule 1.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
thread_wrapper.hpp
1#pragma once
2
3#include "expected.hpp"
4#include "scheduler_policy.hpp"
5#include <optional>
6#include <string>
7#include <thread>
8
9#ifdef _WIN32
10#include <libloaderapi.h>
11#include <windows.h>
12#else
13#include <dirent.h>
14#include <fstream>
15#include <sys/prctl.h>
16#include <sys/resource.h>
17#include <sys/syscall.h>
18#include <unistd.h>
19#endif
20
21namespace threadschedule
22{
23
24namespace detail
25{
27{
28};
30{
31};
32
33template <typename ThreadType, typename OwnershipTag>
35
36// Owning storage: no extra overhead
37template <typename ThreadType>
38class ThreadStorage<ThreadType, OwningTag>
39{
40 protected:
41 ThreadStorage() = default;
42
43 [[nodiscard]] auto underlying() noexcept -> ThreadType&
44 {
45 return thread_;
46 }
47 [[nodiscard]] auto underlying() const noexcept -> ThreadType const&
48 {
49 return thread_;
50 }
51
52 ThreadType thread_;
53};
54
55// Non-owning storage: reference to external thread
56template <typename ThreadType>
57class ThreadStorage<ThreadType, NonOwningTag>
58{
59 protected:
60 ThreadStorage() = default;
61 explicit ThreadStorage(ThreadType& t) : external_thread_(&t)
62 {
63 }
64
65 [[nodiscard]] auto underlying() noexcept -> ThreadType&
66 {
67 return *external_thread_;
68 }
69 [[nodiscard]] auto underlying() const noexcept -> ThreadType const&
70 {
71 return *external_thread_;
72 }
73
74 ThreadType* external_thread_ = nullptr; // non-owning
75};
76} // namespace detail
77
81template <typename ThreadType, typename OwnershipTag = detail::OwningTag>
82class BaseThreadWrapper : protected detail::ThreadStorage<ThreadType, OwnershipTag>
83{
84 public:
85 using native_handle_type = typename ThreadType::native_handle_type;
86 using id = typename ThreadType::id;
87
88 BaseThreadWrapper() = default;
89 explicit BaseThreadWrapper(ThreadType& t) : detail::ThreadStorage<ThreadType, OwnershipTag>(t)
90 {
91 }
92 virtual ~BaseThreadWrapper() = default;
93
94 // Thread management
95 void join()
96 {
97 if (underlying().joinable())
98 {
99 underlying().join();
100 }
101 }
102
103 void detach()
104 {
105 if (underlying().joinable())
106 {
107 underlying().detach();
108 }
109 }
110
111 [[nodiscard]] auto joinable() const noexcept -> bool
112 {
113 return underlying().joinable();
114 }
115 [[nodiscard]] auto get_id() const noexcept -> id
116 {
117 return underlying().get_id();
118 }
119 [[nodiscard]] auto native_handle() noexcept -> native_handle_type
120 {
121 return underlying().native_handle();
122 }
123
124 // Extended functionality
125 [[nodiscard]] auto set_name(std::string const& name) -> expected<void, std::error_code>
126 {
127#ifdef _WIN32
128 // Windows supports longer thread names. Try SetThreadDescription dynamically.
129 auto const handle = native_handle();
130 std::wstring wide_name(name.begin(), name.end());
131
132 using SetThreadDescriptionFn = HRESULT(WINAPI*)(HANDLE, PCWSTR);
133 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
134 if (hMod)
135 {
136 auto set_desc = reinterpret_cast<SetThreadDescriptionFn>(
137 reinterpret_cast<void*>(GetProcAddress(hMod, "SetThreadDescription")));
138 if (set_desc)
139 {
140 if (SUCCEEDED(set_desc(handle, wide_name.c_str())))
142 return expected<void, std::error_code>(unexpect, std::make_error_code(std::errc::invalid_argument));
143 }
144 }
145 // Fallback unavailable
146 return expected<void, std::error_code>(unexpect, std::make_error_code(std::errc::function_not_supported));
147#else
148 if (name.length() > 15)
149 return expected<void, std::error_code>(unexpect, std::make_error_code(std::errc::invalid_argument));
150
151 auto const handle = native_handle();
152 if (pthread_setname_np(handle, name.c_str()) == 0)
153 return {};
154 return expected<void, std::error_code>(unexpect, std::error_code(errno, std::generic_category()));
155#endif
156 }
157
158 [[nodiscard]] auto get_name() const -> std::optional<std::string>
159 {
160#ifdef _WIN32
161 const auto handle = const_cast<BaseThreadWrapper*>(this)->native_handle();
162 using GetThreadDescriptionFn = HRESULT(WINAPI*)(HANDLE, PWSTR*);
163 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
164 if (hMod)
165 {
166 auto get_desc = reinterpret_cast<GetThreadDescriptionFn>(
167 reinterpret_cast<void*>(GetProcAddress(hMod, "GetThreadDescription")));
168 if (get_desc)
169 {
170 PWSTR thread_name = nullptr;
171 HRESULT hr = get_desc(handle, &thread_name);
172 if (SUCCEEDED(hr) && thread_name)
173 {
174 int size = WideCharToMultiByte(CP_UTF8, 0, thread_name, -1, nullptr, 0, nullptr, nullptr);
175 if (size > 0)
176 {
177 std::string result(size - 1, '\0');
178 WideCharToMultiByte(CP_UTF8, 0, thread_name, -1, &result[0], size, nullptr, nullptr);
179 LocalFree(thread_name);
180 return result;
181 }
182 LocalFree(thread_name);
183 }
184 }
185 }
186 return std::nullopt;
187#else
188 char name[16]; // Linux limit + 1
189 auto const handle = const_cast<BaseThreadWrapper*>(this)->native_handle();
190
191 if (pthread_getname_np(handle, name, sizeof(name)) == 0)
192 {
193 return std::string(name);
194 }
195 return std::nullopt;
196#endif
197 }
198
199 [[nodiscard]] auto set_priority(ThreadPriority priority) -> expected<void, std::error_code>
200 {
201#ifdef _WIN32
202 const auto handle = native_handle();
203 // Map ThreadPriority to Windows priority
204 // Windows thread priorities range from -15 (THREAD_PRIORITY_IDLE) to +15 (THREAD_PRIORITY_TIME_CRITICAL)
205 // We'll map the priority value to Windows constants
206 int win_priority;
207 int prio_val = priority.value();
208
209 if (prio_val <= -10)
210 {
211 win_priority = THREAD_PRIORITY_IDLE;
212 }
213 else if (prio_val <= -5)
214 {
215 win_priority = THREAD_PRIORITY_LOWEST;
216 }
217 else if (prio_val < 0)
218 {
219 win_priority = THREAD_PRIORITY_BELOW_NORMAL;
220 }
221 else if (prio_val == 0)
222 {
223 win_priority = THREAD_PRIORITY_NORMAL;
224 }
225 else if (prio_val <= 5)
226 {
227 win_priority = THREAD_PRIORITY_ABOVE_NORMAL;
228 }
229 else if (prio_val <= 10)
230 {
231 win_priority = THREAD_PRIORITY_HIGHEST;
232 }
233 else
234 {
235 win_priority = THREAD_PRIORITY_TIME_CRITICAL;
236 }
237
238 if (SetThreadPriority(handle, win_priority) != 0)
239 return {};
240 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
241#else
242 const auto handle = native_handle();
243 int const policy = SCHED_OTHER;
244
245 auto params_result = SchedulerParams::create_for_policy(SchedulingPolicy::OTHER, priority);
246
247 if (!params_result.has_value())
248 {
249 return unexpected(params_result.error());
250 }
251
252 if (pthread_setschedparam(handle, policy, &params_result.value()) == 0)
253 return {};
254 return unexpected(std::error_code(errno, std::generic_category()));
255#endif
256 }
257
258 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority)
260 {
261#ifdef _WIN32
262 // Windows doesn't have the same scheduling policy concept as Linux
263 // We'll just set the priority and return success
264 return set_priority(priority);
265#else
266 const auto handle = native_handle();
267 int const policy_int = static_cast<int>(policy);
268
269 auto params_result = SchedulerParams::create_for_policy(policy, priority);
270 if (!params_result.has_value())
271 {
272 return unexpected(params_result.error());
273 }
274
275 if (pthread_setschedparam(handle, policy_int, &params_result.value()) == 0)
276 return {};
277 return unexpected(std::error_code(errno, std::generic_category()));
278#endif
279 }
280
281 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) -> expected<void, std::error_code>
282 {
283#ifdef _WIN32
284 const auto handle = native_handle();
285 // Prefer Group Affinity if available
286 using SetThreadGroupAffinityFn = BOOL(WINAPI*)(HANDLE, const GROUP_AFFINITY*, PGROUP_AFFINITY);
287 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
288 if (hMod)
289 {
290 auto set_group_affinity = reinterpret_cast<SetThreadGroupAffinityFn>(
291 reinterpret_cast<void*>(GetProcAddress(hMod, "SetThreadGroupAffinity")));
292 if (set_group_affinity && affinity.has_any())
293 {
294 GROUP_AFFINITY ga{};
295 ga.Mask = static_cast<KAFFINITY>(affinity.get_mask());
296 ga.Group = affinity.get_group();
297 if (set_group_affinity(handle, &ga, nullptr) != 0)
298 return {};
299 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
300 }
301 }
302 // Fallback to legacy mask (single-group systems)
303 DWORD_PTR mask = static_cast<DWORD_PTR>(affinity.get_mask());
304 if (SetThreadAffinityMask(handle, mask) != 0)
305 return {};
306 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
307#else
308 const auto handle = native_handle();
309 if (pthread_setaffinity_np(handle, sizeof(cpu_set_t), &affinity.native_handle()) == 0)
310 return {};
311 return unexpected(std::error_code(errno, std::generic_category()));
312#endif
313 }
314
315 [[nodiscard]] auto get_affinity() const -> std::optional<ThreadAffinity>
316 {
317#ifdef _WIN32
318 const auto handle = const_cast<BaseThreadWrapper*>(this)->native_handle();
319 using GetThreadGroupAffinityFn = BOOL(WINAPI*)(HANDLE, PGROUP_AFFINITY);
320 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
321 if (hMod)
322 {
323 auto get_group_affinity = reinterpret_cast<GetThreadGroupAffinityFn>(
324 reinterpret_cast<void*>(GetProcAddress(hMod, "GetThreadGroupAffinity")));
325 if (get_group_affinity)
326 {
327 GROUP_AFFINITY ga{};
328 if (get_group_affinity(handle, &ga) != 0)
329 {
330 ThreadAffinity affinity;
331 for (int i = 0; i < 64; ++i)
332 {
333 if ((ga.Mask & (static_cast<KAFFINITY>(1) << i)) != 0)
334 {
335 affinity.add_cpu(static_cast<int>(ga.Group) * 64 + i);
336 }
337 }
338 if (affinity.has_any())
339 {
340 return affinity;
341 }
342 return std::nullopt;
343 }
344 }
345 return std::nullopt;
346 }
347#else
348 ThreadAffinity affinity;
349 auto const handle = const_cast<BaseThreadWrapper*>(this)->native_handle();
350
351 if (pthread_getaffinity_np(handle, sizeof(cpu_set_t), &affinity.native_handle()) == 0)
352 {
353 return affinity;
354 }
355 return std::nullopt;
356#endif
357 }
358
359 // Nice value (process-level, affects all threads)
360 static auto set_nice_value(int nice_value) -> bool
361 {
362#ifdef _WIN32
363 // Windows has process priority classes, not nice values
364 // We'll use SetPriorityClass for the process
365 DWORD priority_class;
366 if (nice_value <= -15)
367 {
368 priority_class = HIGH_PRIORITY_CLASS;
369 }
370 else if (nice_value <= -10)
371 {
372 priority_class = ABOVE_NORMAL_PRIORITY_CLASS;
373 }
374 else if (nice_value < 10)
375 {
376 priority_class = NORMAL_PRIORITY_CLASS;
377 }
378 else if (nice_value < 19)
379 {
380 priority_class = BELOW_NORMAL_PRIORITY_CLASS;
381 }
382 else
383 {
384 priority_class = IDLE_PRIORITY_CLASS;
385 }
386 return SetPriorityClass(GetCurrentProcess(), priority_class) != 0;
387#else
388 return setpriority(PRIO_PROCESS, 0, nice_value) == 0;
389#endif
390 }
391
392 static auto get_nice_value() -> std::optional<int>
393 {
394#ifdef _WIN32
395 // Get Windows process priority class and map to nice value
396 DWORD priority_class = GetPriorityClass(GetCurrentProcess());
397 if (priority_class == 0)
398 {
399 return std::nullopt;
400 }
401
402 // Map Windows priority class to nice value
403 switch (priority_class)
404 {
405 case HIGH_PRIORITY_CLASS:
406 return -15;
407 case ABOVE_NORMAL_PRIORITY_CLASS:
408 return -10;
409 case NORMAL_PRIORITY_CLASS:
410 return 0;
411 case BELOW_NORMAL_PRIORITY_CLASS:
412 return 10;
413 case IDLE_PRIORITY_CLASS:
414 return 19;
415 default:
416 return 0;
417 }
418#else
419 errno = 0;
420 int const nice = getpriority(PRIO_PROCESS, 0);
421 if (errno == 0)
422 {
423 return nice;
424 }
425 return std::nullopt;
426#endif
427 }
428
429 protected:
430 using detail::ThreadStorage<ThreadType, OwnershipTag>::underlying;
431 using detail::ThreadStorage<ThreadType, OwnershipTag>::ThreadStorage;
432};
433
437class ThreadWrapper : public BaseThreadWrapper<std::thread, detail::OwningTag>
438{
439 public:
440 ThreadWrapper() = default;
441
442 // Construct by taking ownership of an existing std::thread (move)
443 ThreadWrapper(std::thread&& t) noexcept
444 {
445 this->underlying() = std::move(t);
446 }
447
448 template <typename F, typename... Args>
449 explicit ThreadWrapper(F&& f, Args&&... args) : BaseThreadWrapper()
450 {
451 this->underlying() = std::thread(std::forward<F>(f), std::forward<Args>(args)...);
452 }
453
454 ThreadWrapper(ThreadWrapper const&) = delete;
455 auto operator=(ThreadWrapper const&) -> ThreadWrapper& = delete;
456
457 ThreadWrapper(ThreadWrapper&& other) noexcept
458 {
459 this->underlying() = std::move(other.underlying());
460 }
461
462 auto operator=(ThreadWrapper&& other) noexcept -> ThreadWrapper&
463 {
464 if (this != &other)
465 {
466 if (this->underlying().joinable())
467 {
468 this->underlying().join();
469 }
470 this->underlying() = std::move(other.underlying());
471 }
472 return *this;
473 }
474
475 ~ThreadWrapper() override
476 {
477 if (this->underlying().joinable())
478 {
479 this->underlying().join();
480 }
481 }
482
483 // Ownership transfer to std::thread for APIs that take plain std::thread
484 auto release() noexcept -> std::thread
485 {
486 return std::move(this->underlying());
487 }
488
489 explicit operator std::thread() && noexcept
490 {
491 return std::move(this->underlying());
492 }
493
494 // Factory methods
495 template <typename F, typename... Args>
496 static auto create_with_config(std::string const& name, SchedulingPolicy policy, ThreadPriority priority, F&& f,
497 Args&&... args) -> ThreadWrapper
498 {
499
500 ThreadWrapper wrapper(std::forward<F>(f), std::forward<Args>(args)...);
501 if (auto r = wrapper.set_name(name); !r.has_value())
502 {
503 }
504 if (auto r = wrapper.set_scheduling_policy(policy, priority); !r.has_value())
505 {
506 }
507 return wrapper;
508 }
509};
510
511// Non-owning view over std::thread
512class ThreadWrapperView : public BaseThreadWrapper<std::thread, detail::NonOwningTag>
513{
514 public:
515 ThreadWrapperView(std::thread& t) : BaseThreadWrapper<std::thread, detail::NonOwningTag>(t)
516 {
517 }
518
519 // Non-owning access to the underlying std::thread
520 auto get() noexcept -> std::thread&
521 {
522 return this->underlying();
523 }
524 [[nodiscard]] auto get() const noexcept -> std::thread const&
525 {
526 return this->underlying();
527 }
528};
529
533#if __cplusplus >= 202002L
534class JThreadWrapper : public BaseThreadWrapper<std::jthread, detail::OwningTag>
535{
536 public:
537 JThreadWrapper() = default;
538
539 // Construct by taking ownership of an existing std::jthread (move)
540 JThreadWrapper(std::jthread&& t) noexcept : BaseThreadWrapper()
541 {
542 this->underlying() = std::move(t);
543 }
544
545 // Ownership transfer to std::jthread for APIs that take plain std::jthread
546 auto release() noexcept -> std::jthread
547 {
548 return std::move(this->underlying());
549 }
550
551 explicit operator std::jthread() && noexcept
552 {
553 return std::move(this->underlying());
554 }
555
556 template <typename F, typename... Args>
557 explicit JThreadWrapper(F&& f, Args&&... args) : BaseThreadWrapper()
558 {
559 this->underlying() = std::jthread(std::forward<F>(f), std::forward<Args>(args)...);
560 }
561
562 JThreadWrapper(JThreadWrapper const&) = delete;
563 JThreadWrapper& operator=(JThreadWrapper const&) = delete;
564
565 JThreadWrapper(JThreadWrapper&& other) noexcept : BaseThreadWrapper()
566 {
567 this->underlying() = std::move(other.underlying());
568 }
569
570 JThreadWrapper& operator=(JThreadWrapper&& other) noexcept
571 {
572 if (this != &other)
573 {
574 this->underlying() = std::move(other.underlying());
575 }
576 return *this;
577 }
578
579 // jthread-specific functionality
580 void request_stop()
581 {
582 this->underlying().request_stop();
583 }
584 bool stop_requested()
585 {
586 return this->underlying().get_stop_token().stop_requested();
587 }
588 std::stop_token get_stop_token() const
589 {
590 return this->underlying().get_stop_token();
591 }
592 std::stop_source get_stop_source()
593 {
594 return this->underlying().get_stop_source();
595 }
596
597 // Factory methods
598 template <typename F, typename... Args>
599 static JThreadWrapper create_with_config(std::string const& name, SchedulingPolicy policy, ThreadPriority priority,
600 F&& f, Args&&... args)
601 {
602
603 JThreadWrapper wrapper(std::forward<F>(f), std::forward<Args>(args)...);
604 if (auto r = wrapper.set_name(name); !r.has_value())
605 {
606 }
607 if (auto r = wrapper.set_scheduling_policy(policy, priority); !r.has_value())
608 {
609 }
610 return wrapper;
611 }
612};
613
614// Non-owning view over std::jthread (C++20)
615class JThreadWrapperView : public BaseThreadWrapper<std::jthread, detail::NonOwningTag>
616{
617 public:
618 JThreadWrapperView(std::jthread& t) : BaseThreadWrapper<std::jthread, detail::NonOwningTag>(t)
619 {
620 }
621
622 void request_stop()
623 {
624 this->underlying().request_stop();
625 }
626 bool stop_requested()
627 {
628 return this->underlying().get_stop_token().stop_requested();
629 }
630 std::stop_token get_stop_token() const
631 {
632 return this->underlying().get_stop_token();
633 }
634 std::stop_source get_stop_source()
635 {
636 return this->underlying().get_stop_source();
637 }
638
639 // Non-owning access to the underlying std::jthread
640 auto get() noexcept -> std::jthread&
641 {
642 return this->underlying();
643 }
644 [[nodiscard]] auto get() const noexcept -> std::jthread const&
645 {
646 return this->underlying();
647 }
648};
649#else
650// Fallback for compilers without C++20 support
651using JThreadWrapper = ThreadWrapper;
652using JThreadWrapperView = ThreadWrapperView;
653#endif
654
655class ThreadByNameView
656{
657 public:
658#ifdef _WIN32
659 using native_handle_type = void*; // unsupported placeholder
660#else
661 using native_handle_type = pid_t; // Linux TID
662#endif
663
664 explicit ThreadByNameView(const std::string& name)
665 {
666#ifdef _WIN32
667 // Not supported on Windows in this implementation
668 (void)name;
669#else
670 DIR* dir = opendir("/proc/self/task");
671 if (dir == nullptr)
672 return;
673 struct dirent* entry = nullptr;
674 while ((entry = readdir(dir)) != nullptr)
675 {
676 if (entry->d_name[0] == '.')
677 continue;
678 std::string tid_str(entry->d_name);
679 std::string path = std::string("/proc/self/task/") + tid_str + "/comm";
680 std::ifstream in(path);
681 if (!in)
682 continue;
683 std::string current;
684 std::getline(in, current);
685 if (!current.empty() && current.back() == '\n')
686 current.pop_back();
687 if (current == name)
688 {
689 handle_ = static_cast<pid_t>(std::stoi(tid_str));
690 break;
691 }
692 }
693 closedir(dir);
694#endif
695 }
696
697 [[nodiscard]] auto found() const noexcept -> bool
698 {
699#ifdef _WIN32
700 return false;
701#else
702 return handle_ > 0;
703#endif
704 }
705
706 [[nodiscard]] auto set_name(std::string const& name) const -> expected<void, std::error_code>
707 {
708#ifdef _WIN32
709 return unexpected(std::make_error_code(std::errc::function_not_supported));
710#else
711 if (!found())
712 return unexpected(std::make_error_code(std::errc::no_such_process));
713 if (name.length() > 15)
714 return unexpected(std::make_error_code(std::errc::invalid_argument));
715 std::string path = std::string("/proc/self/task/") + std::to_string(handle_) + "/comm";
716 std::ofstream out(path);
717 if (!out)
718 return unexpected(std::error_code(errno, std::generic_category()));
719 out << name;
720 out.flush();
721 if (!out)
722 return unexpected(std::error_code(errno, std::generic_category()));
723 return {};
724#endif
725 }
726
727 [[nodiscard]] auto get_name() const -> std::optional<std::string>
728 {
729#ifdef _WIN32
730 return std::nullopt;
731#else
732 if (!found())
733 return std::nullopt;
734 std::string path = std::string("/proc/self/task/") + std::to_string(handle_) + "/comm";
735 std::ifstream in(path);
736 if (!in)
737 return std::nullopt;
738 std::string current;
739 std::getline(in, current);
740 if (!current.empty() && current.back() == '\n')
741 current.pop_back();
742 return current;
743#endif
744 }
745
746 [[nodiscard]] auto native_handle() const noexcept -> native_handle_type
747 {
748 return handle_;
749 }
750
751 [[nodiscard]] auto set_priority(ThreadPriority priority) const -> expected<void, std::error_code>
752 {
753#ifdef _WIN32
754 return unexpected(std::make_error_code(std::errc::function_not_supported));
755#else
756 if (!found())
757 return unexpected(std::make_error_code(std::errc::no_such_process));
758 int const policy = SCHED_OTHER;
759 auto params_result = SchedulerParams::create_for_policy(SchedulingPolicy::OTHER, priority);
760 if (!params_result.has_value())
761 return unexpected(params_result.error());
762 if (sched_setscheduler(handle_, policy, &params_result.value()) == 0)
763 return {};
764 return unexpected(std::error_code(errno, std::generic_category()));
765#endif
766 }
767
768 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const
770 {
771#ifdef _WIN32
772 return unexpected(std::make_error_code(std::errc::function_not_supported));
773#else
774 if (!found())
775 return unexpected(std::make_error_code(std::errc::no_such_process));
776 int policy_int = static_cast<int>(policy);
777 auto params_result = SchedulerParams::create_for_policy(policy, priority);
778 if (!params_result.has_value())
779 return unexpected(params_result.error());
780 if (sched_setscheduler(handle_, policy_int, &params_result.value()) == 0)
781 return {};
782 return unexpected(std::error_code(errno, std::generic_category()));
783#endif
784 }
785
786 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) const -> expected<void, std::error_code>
787 {
788#ifdef _WIN32
789 return unexpected(std::make_error_code(std::errc::function_not_supported));
790#else
791 if (!found())
792 return unexpected(std::make_error_code(std::errc::no_such_process));
793 if (sched_setaffinity(handle_, sizeof(cpu_set_t), &affinity.native_handle()) == 0)
794 return {};
795 return unexpected(std::error_code(errno, std::generic_category()));
796#endif
797 }
798
799 private:
800#ifdef _WIN32
801 native_handle_type handle_ = nullptr;
802#else
803 native_handle_type handle_ = 0;
804#endif
805};
806
807// Static hardware information
809{
810 public:
811 static auto hardware_concurrency() -> unsigned int
812 {
813 return std::thread::hardware_concurrency();
814 }
815
816 static auto get_thread_id()
817 {
818#ifdef _WIN32
819 return GetCurrentThreadId();
820#else
821 return static_cast<pid_t>(syscall(SYS_gettid));
822#endif
823 }
824
825 static auto get_current_policy() -> std::optional<SchedulingPolicy>
826 {
827#ifdef _WIN32
828 // Windows doesn't have Linux-style scheduling policies
829 // Return OTHER as a default
830 return SchedulingPolicy::OTHER;
831#else
832 const int policy = sched_getscheduler(0);
833 if (policy == -1)
834 {
835 return std::nullopt;
836 }
837 return static_cast<SchedulingPolicy>(policy);
838#endif
839 }
840
841 static auto get_current_priority() -> std::optional<int>
842 {
843#ifdef _WIN32
844 HANDLE thread = GetCurrentThread();
845 int priority = GetThreadPriority(thread);
846 if (priority == THREAD_PRIORITY_ERROR_RETURN)
847 {
848 return std::nullopt;
849 }
850 return priority;
851#else
852 sched_param param;
853 if (sched_getparam(0, &param) == 0)
854 {
855 return param.sched_priority;
856 }
857 return std::nullopt;
858#endif
859 }
860};
861
862} // namespace threadschedule
Base thread wrapper with common functionality.
Thread priority wrapper with validation.
Enhanced std::thread wrapper.