ThreadSchedule 2.2.0
Modern C++ thread management library
Loading...
Searching...
No Matches
thread_wrapper.hpp
Go to the documentation of this file.
1#pragma once
2
7
8#include "expected.hpp"
10#include <memory>
11#include <optional>
12#include <string>
13#include <thread>
14
15#ifdef _WIN32
16# include <libloaderapi.h>
17# include <windows.h>
18#else
19# include <dirent.h>
20# include <fstream>
21# include <sys/prctl.h>
22# include <sys/resource.h>
23# include <sys/syscall.h>
24# include <unistd.h>
25#endif
26
27namespace threadschedule
28{
29
30namespace detail
31{
34{
35};
36
38{
39};
40
41template <typename ThreadType, typename OwnershipTag>
43
59template <typename ThreadType>
60class ThreadStorage<ThreadType, OwningTag>
61{
62 protected:
63 ThreadStorage() = default;
64
65 [[nodiscard]] auto underlying() noexcept -> ThreadType&
66 {
67 return thread_;
68 }
69 [[nodiscard]] auto underlying() const noexcept -> ThreadType const&
70 {
71 return thread_;
72 }
73
74 ThreadType thread_;
75};
76
94template <typename ThreadType>
95class ThreadStorage<ThreadType, NonOwningTag>
96{
97 protected:
98 ThreadStorage() = default;
99 explicit ThreadStorage(ThreadType& t) : external_thread_(&t)
100 {
101 }
102
103 [[nodiscard]] auto underlying() noexcept -> ThreadType&
104 {
105 return *external_thread_;
106 }
107 [[nodiscard]] auto underlying() const noexcept -> ThreadType const&
108 {
109 return *external_thread_;
110 }
111
112 ThreadType* external_thread_ = nullptr; // non-owning
113};
114
115template <typename ThreadLike>
116inline auto configure_thread(ThreadLike& thread, std::string const& name, SchedulingPolicy policy,
118{
119 bool success = true;
120 if (!thread.set_name(name).has_value())
121 success = false;
122 if (!thread.set_scheduling_policy(policy, priority).has_value())
123 success = false;
124 if (success)
125 return {};
126 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
127}
128} // namespace detail
129
182template <typename ThreadType, typename OwnershipTag = detail::OwningTag>
183class BaseThreadWrapper : protected detail::ThreadStorage<ThreadType, OwnershipTag>
184{
185 public:
186 using native_handle_type = typename ThreadType::native_handle_type;
187 using id = typename ThreadType::id;
188
189 BaseThreadWrapper() = default;
190 explicit BaseThreadWrapper(ThreadType& t) : detail::ThreadStorage<ThreadType, OwnershipTag>(t)
191 {
192 }
193 virtual ~BaseThreadWrapper() = default;
194
195 // Thread management
196 void join()
197 {
198 if (underlying().joinable())
199 {
200 underlying().join();
201 }
202 }
203
204 void detach()
205 {
206 if (underlying().joinable())
207 {
208 underlying().detach();
209 }
210 }
211
212 [[nodiscard]] auto joinable() const noexcept -> bool
213 {
214 return underlying().joinable();
215 }
216 [[nodiscard]] auto get_id() const noexcept -> id
217 {
218 return underlying().get_id();
219 }
220 [[nodiscard]] auto native_handle() noexcept -> native_handle_type
221 {
222 return underlying().native_handle();
223 }
224
225 [[nodiscard]] auto set_name(std::string const& name) -> expected<void, std::error_code>
226 {
227 return detail::apply_name(native_handle(), name);
228 }
229
230 [[nodiscard]] auto get_name() const -> std::optional<std::string>
231 {
232 return detail::read_name(const_cast<BaseThreadWrapper*>(this)->native_handle());
233 }
234
236 {
237 return detail::apply_priority(native_handle(), priority);
238 }
239
240 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority)
242 {
243 return detail::apply_scheduling_policy(native_handle(), policy, priority);
244 }
245
246 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) -> expected<void, std::error_code>
247 {
248 return detail::apply_affinity(native_handle(), affinity);
249 }
250
251 [[nodiscard]] auto get_affinity() const -> std::optional<ThreadAffinity>
252 {
253 return detail::read_affinity(const_cast<BaseThreadWrapper*>(this)->native_handle());
254 }
255
256 // Nice value (process-level, affects all threads)
257 static auto set_nice_value(int nice_value) -> bool
258 {
259#ifdef _WIN32
260 // Windows has process priority classes, not nice values
261 // We'll use SetPriorityClass for the process
262 DWORD priority_class;
263 if (nice_value <= -15)
264 {
265 priority_class = HIGH_PRIORITY_CLASS;
266 }
267 else if (nice_value <= -10)
268 {
269 priority_class = ABOVE_NORMAL_PRIORITY_CLASS;
270 }
271 else if (nice_value < 10)
272 {
273 priority_class = NORMAL_PRIORITY_CLASS;
274 }
275 else if (nice_value < 19)
276 {
277 priority_class = BELOW_NORMAL_PRIORITY_CLASS;
278 }
279 else
280 {
281 priority_class = IDLE_PRIORITY_CLASS;
282 }
283 return SetPriorityClass(GetCurrentProcess(), priority_class) != 0;
284#else
285 return setpriority(PRIO_PROCESS, 0, nice_value) == 0;
286#endif
287 }
288
289 static auto get_nice_value() -> std::optional<int>
290 {
291#ifdef _WIN32
292 // Get Windows process priority class and map to nice value
293 DWORD priority_class = GetPriorityClass(GetCurrentProcess());
294 if (priority_class == 0)
295 {
296 return std::nullopt;
297 }
298
299 // Map Windows priority class to nice value
300 switch (priority_class)
301 {
302 case HIGH_PRIORITY_CLASS:
303 return -15;
304 case ABOVE_NORMAL_PRIORITY_CLASS:
305 return -10;
306 case NORMAL_PRIORITY_CLASS:
307 return 0;
308 case BELOW_NORMAL_PRIORITY_CLASS:
309 return 10;
310 case IDLE_PRIORITY_CLASS:
311 return 19;
312 default:
313 return 0;
314 }
315#else
316 errno = 0;
317 int const nice = getpriority(PRIO_PROCESS, 0);
318 if (errno == 0)
319 {
320 return nice;
321 }
322 return std::nullopt;
323#endif
324 }
325
326 protected:
327 using detail::ThreadStorage<ThreadType, OwnershipTag>::underlying;
328 using detail::ThreadStorage<ThreadType, OwnershipTag>::ThreadStorage;
329};
330
367class ThreadWrapper : public BaseThreadWrapper<std::thread, detail::OwningTag>
368{
369 public:
370 ThreadWrapper() = default;
371
372 // Construct by taking ownership of an existing std::thread (move)
373 ThreadWrapper(std::thread&& t) noexcept
374 {
375 this->underlying() = std::move(t);
376 }
377
378 template <typename F, typename... Args>
379 explicit ThreadWrapper(F&& f, Args&&... args) : BaseThreadWrapper()
380 {
381 this->underlying() = std::thread(std::forward<F>(f), std::forward<Args>(args)...);
382 }
383
384 ThreadWrapper(ThreadWrapper const&) = delete;
385 auto operator=(ThreadWrapper const&) -> ThreadWrapper& = delete;
386
387 ThreadWrapper(ThreadWrapper&& other) noexcept
388 {
389 this->underlying() = std::move(other.underlying());
390 }
391
392 auto operator=(ThreadWrapper&& other) noexcept -> ThreadWrapper&
393 {
394 if (this != &other)
395 {
396 if (this->underlying().joinable())
397 {
398 this->underlying().join();
399 }
400 this->underlying() = std::move(other.underlying());
401 }
402 return *this;
403 }
404
405 ~ThreadWrapper() override
406 {
407 if (this->underlying().joinable())
408 {
409 this->underlying().join();
410 }
411 }
412
413 // Ownership transfer to std::thread for APIs that take plain std::thread
414 auto release() noexcept -> std::thread
415 {
416 return std::move(this->underlying());
417 }
418
419 explicit operator std::thread() && noexcept
420 {
421 return std::move(this->underlying());
422 }
423
424 // Factory methods
425 template <typename F, typename... Args>
426 static auto create_with_config(std::string const& name, SchedulingPolicy policy, ThreadPriority priority, F&& f,
427 Args&&... args) -> ThreadWrapper
428 {
429 ThreadWrapper wrapper(std::forward<F>(f), std::forward<Args>(args)...);
430 (void)wrapper.set_name(name);
431 (void)wrapper.set_scheduling_policy(policy, priority);
432 return wrapper;
433 }
434};
435
455class ThreadWrapperView : public BaseThreadWrapper<std::thread, detail::NonOwningTag>
456{
457 public:
458 ThreadWrapperView(std::thread& t) : BaseThreadWrapper<std::thread, detail::NonOwningTag>(t)
459 {
460 }
461
462 // Non-owning access to the underlying std::thread
463 auto get() noexcept -> std::thread&
464 {
465 return this->underlying();
466 }
467 [[nodiscard]] auto get() const noexcept -> std::thread const&
468 {
469 return this->underlying();
470 }
471};
472
510#if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
511class JThreadWrapper : public BaseThreadWrapper<std::jthread, detail::OwningTag>
512{
513 public:
514 JThreadWrapper() = default;
515
516 // Construct by taking ownership of an existing std::jthread (move)
517 JThreadWrapper(std::jthread&& t) noexcept : BaseThreadWrapper()
518 {
519 this->underlying() = std::move(t);
520 }
521
522 // Ownership transfer to std::jthread for APIs that take plain std::jthread
523 auto release() noexcept -> std::jthread
524 {
525 return std::move(this->underlying());
526 }
527
528 explicit operator std::jthread() && noexcept
529 {
530 return std::move(this->underlying());
531 }
532
533 template <typename F, typename... Args>
534 explicit JThreadWrapper(F&& f, Args&&... args) : BaseThreadWrapper()
535 {
536 this->underlying() = std::jthread(std::forward<F>(f), std::forward<Args>(args)...);
537 }
538
539 JThreadWrapper(JThreadWrapper const&) = delete;
540 auto operator=(JThreadWrapper const&) -> JThreadWrapper& = delete;
541
542 JThreadWrapper(JThreadWrapper&& other) noexcept
543 {
544 this->underlying() = std::move(other.underlying());
545 }
546
547 auto operator=(JThreadWrapper&& other) noexcept -> JThreadWrapper&
548 {
549 if (this != &other)
550 {
551 this->underlying() = std::move(other.underlying());
552 }
553 return *this;
554 }
555
556 // jthread-specific functionality
557 void request_stop()
558 {
559 this->underlying().request_stop();
560 }
561 [[nodiscard]] auto stop_requested() const noexcept -> bool
562 {
563 return this->underlying().get_stop_token().stop_requested();
564 }
565 [[nodiscard]] auto get_stop_token() const noexcept -> std::stop_token
566 {
567 return this->underlying().get_stop_token();
568 }
569 [[nodiscard]] auto get_stop_source() noexcept -> std::stop_source
570 {
571 return this->underlying().get_stop_source();
572 }
573
574 // Factory methods
575 template <typename F, typename... Args>
576 static auto create_with_config(std::string const& name, SchedulingPolicy policy, ThreadPriority priority, F&& f,
577 Args&&... args) -> JThreadWrapper
578 {
579 JThreadWrapper wrapper(std::forward<F>(f), std::forward<Args>(args)...);
580 (void)wrapper.set_name(name);
581 (void)wrapper.set_scheduling_policy(policy, priority);
582 return wrapper;
583 }
584};
585
610class JThreadWrapperView : public BaseThreadWrapper<std::jthread, detail::NonOwningTag>
611{
612 public:
613 JThreadWrapperView(std::jthread& t) : BaseThreadWrapper<std::jthread, detail::NonOwningTag>(t)
614 {
615 }
616
617 void request_stop()
618 {
619 this->underlying().request_stop();
620 }
621 [[nodiscard]] auto stop_requested() const noexcept -> bool
622 {
623 return this->underlying().get_stop_token().stop_requested();
624 }
625 [[nodiscard]] auto get_stop_token() const noexcept -> std::stop_token
626 {
627 return this->underlying().get_stop_token();
628 }
629 [[nodiscard]] auto get_stop_source() noexcept -> std::stop_source
630 {
631 return this->underlying().get_stop_source();
632 }
633
634 // Non-owning access to the underlying std::jthread
635 auto get() noexcept -> std::jthread&
636 {
637 return this->underlying();
638 }
639 [[nodiscard]] auto get() const noexcept -> std::jthread const&
640 {
641 return this->underlying();
642 }
643};
644#else
645// Fallback for compilers without C++20 support
648#endif // C++20
649
684{
685 public:
686#ifdef _WIN32
687 using native_handle_type = void*; // unsupported placeholder
688#else
689 using native_handle_type = Tid; // Linux TID
690#endif
691
692 explicit ThreadByNameView(const std::string& name)
693 {
694#ifdef _WIN32
695 // Not supported on Windows in this implementation
696 (void)name;
697#else
698 DIR* dir = opendir("/proc/self/task");
699 if (dir == nullptr)
700 return;
701 struct dirent* entry = nullptr;
702 while ((entry = readdir(dir)) != nullptr)
703 {
704 if (entry->d_name[0] == '.')
705 continue;
706 std::string tid_str(entry->d_name);
707 std::string path = std::string("/proc/self/task/") + tid_str + "/comm";
708 std::ifstream in(path);
709 if (!in)
710 continue;
711 std::string current;
712 std::getline(in, current);
713 if (!current.empty() && current.back() == '\n')
714 current.pop_back();
715 if (current == name)
716 {
717 handle_ = static_cast<pid_t>(std::stoi(tid_str));
718 break;
719 }
720 }
721 closedir(dir);
722#endif
723 }
724
725 [[nodiscard]] auto found() const noexcept -> bool
726 {
727#ifdef _WIN32
728 return false;
729#else
730 return handle_ > 0;
731#endif
732 }
733
734 [[nodiscard]] auto set_name(std::string const& name) const -> expected<void, std::error_code>
735 {
736#ifdef _WIN32
737 return unexpected(std::make_error_code(std::errc::function_not_supported));
738#else
739 if (!found())
740 return unexpected(std::make_error_code(std::errc::no_such_process));
741 return detail::apply_name(handle_, name);
742#endif
743 }
744
745 [[nodiscard]] auto get_name() const -> std::optional<std::string>
746 {
747#ifdef _WIN32
748 return std::nullopt;
749#else
750 if (!found())
751 return std::nullopt;
752 return detail::read_name(handle_);
753#endif
754 }
755
756 [[nodiscard]] auto native_handle() const noexcept -> native_handle_type
757 {
758 return handle_;
759 }
760
761 [[nodiscard]] auto set_priority(ThreadPriority priority) const -> expected<void, std::error_code>
762 {
763#ifdef _WIN32
764 return unexpected(std::make_error_code(std::errc::function_not_supported));
765#else
766 if (!found())
767 return unexpected(std::make_error_code(std::errc::no_such_process));
768 return detail::apply_priority(handle_, priority);
769#endif
770 }
771
772 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const
774 {
775#ifdef _WIN32
776 return unexpected(std::make_error_code(std::errc::function_not_supported));
777#else
778 if (!found())
779 return unexpected(std::make_error_code(std::errc::no_such_process));
780 return detail::apply_scheduling_policy(handle_, policy, priority);
781#endif
782 }
783
784 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) const -> expected<void, std::error_code>
785 {
786#ifdef _WIN32
787 return unexpected(std::make_error_code(std::errc::function_not_supported));
788#else
789 if (!found())
790 return unexpected(std::make_error_code(std::errc::no_such_process));
791 return detail::apply_affinity(handle_, affinity);
792#endif
793 }
794
795 private:
796#ifdef _WIN32
797 native_handle_type handle_ = nullptr;
798#else
799 native_handle_type handle_ = 0;
800#endif
801};
802
827{
828 public:
829#ifdef _WIN32
830 using native_handle_type = HANDLE;
831#else
832 using native_handle_type = pthread_t;
833#endif
834
836 {
837 bind_current_thread_handle();
838 }
839
840 explicit ThreadInfo(Tid tid) : tid_(tid)
841 {
842 }
843
844 [[nodiscard]] auto thread_id() const noexcept -> Tid
845 {
846 return tid_;
847 }
848
849 [[nodiscard]] auto set_name(std::string const& name) const -> expected<void, std::error_code>
850 {
851 if (has_native_handle())
852 return detail::apply_name(native_handle(), name);
853 return detail::apply_name(tid_, name);
854 }
855
856 [[nodiscard]] auto get_name() const -> std::optional<std::string>
857 {
858 if (has_native_handle())
859 return detail::read_name(native_handle());
860 return detail::read_name(tid_);
861 }
862
863 [[nodiscard]] auto set_priority(ThreadPriority priority) const -> expected<void, std::error_code>
864 {
865 if (has_native_handle())
866 return detail::apply_priority(native_handle(), priority);
867 return detail::apply_priority(tid_, priority);
868 }
869
870 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const
872 {
873 if (has_native_handle())
874 return detail::apply_scheduling_policy(native_handle(), policy, priority);
875 return detail::apply_scheduling_policy(tid_, policy, priority);
876 }
877
878 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) const -> expected<void, std::error_code>
879 {
880 if (has_native_handle())
881 return detail::apply_affinity(native_handle(), affinity);
882 return detail::apply_affinity(tid_, affinity);
883 }
884
885 [[nodiscard]] auto get_affinity() const -> std::optional<ThreadAffinity>
886 {
887 if (has_native_handle())
888 return detail::read_affinity(native_handle());
889 return detail::read_affinity(tid_);
890 }
891
892 [[nodiscard]] auto get_policy() const -> std::optional<SchedulingPolicy>
893 {
894 if (has_native_handle())
895 return detail::read_scheduling_policy(native_handle());
897 }
898
899 [[nodiscard]] auto get_priority() const -> std::optional<int>
900 {
901 if (has_native_handle())
902 return detail::read_priority(native_handle());
903 return detail::read_priority(tid_);
904 }
905
906 static auto hardware_concurrency() -> unsigned int
907 {
908 return std::thread::hardware_concurrency();
909 }
910
911 static auto get_thread_id() -> Tid
912 {
913#ifdef _WIN32
914 return GetCurrentThreadId();
915#else
916 return static_cast<pid_t>(syscall(SYS_gettid));
917#endif
918 }
919
920 static auto get_current_policy() -> std::optional<SchedulingPolicy>
921 {
922 return ThreadInfo().get_policy();
923 }
924
925 static auto get_current_priority() -> std::optional<int>
926 {
927 return ThreadInfo().get_priority();
928 }
929
930 private:
931 void bind_current_thread_handle()
932 {
933#ifdef _WIN32
934 HANDLE real_handle = nullptr;
935 if (DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &real_handle,
936 THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION, FALSE, 0) != 0)
937 {
938 nativeHandle_ = real_handle;
939 nativeHandleOwner_ = std::shared_ptr<void>(real_handle, [](void* handle) {
940 if (handle)
941 CloseHandle(static_cast<HANDLE>(handle));
942 });
943 hasNativeHandle_ = true;
944 }
945#else
946 nativeHandle_ = pthread_self();
947 hasNativeHandle_ = true;
948#endif
949 }
950
951 [[nodiscard]] auto has_native_handle() const noexcept -> bool
952 {
953 return hasNativeHandle_;
954 }
955
956 [[nodiscard]] auto native_handle() const noexcept -> native_handle_type
957 {
958 return nativeHandle_;
959 }
960
961 Tid tid_{};
962#ifdef _WIN32
963 native_handle_type nativeHandle_ = nullptr;
964 std::shared_ptr<void> nativeHandleOwner_;
965#else
966 native_handle_type nativeHandle_{};
967#endif
968 bool hasNativeHandle_{false};
969};
970
971} // namespace threadschedule
Polymorphic base providing common thread management operations.
auto native_handle() noexcept -> native_handle_type
static auto get_nice_value() -> std::optional< int >
static auto set_nice_value(int nice_value) -> bool
typename ThreadType::native_handle_type native_handle_type
auto get_affinity() const -> std::optional< ThreadAffinity >
auto get_name() const -> std::optional< std::string >
auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) -> expected< void, std::error_code >
auto get_id() const noexcept -> id
auto set_name(std::string const &name) -> expected< void, std::error_code >
auto set_priority(ThreadPriority priority) -> expected< void, std::error_code >
virtual ~BaseThreadWrapper()=default
auto joinable() const noexcept -> bool
auto set_affinity(ThreadAffinity const &affinity) -> expected< void, std::error_code >
Manages a set of CPU indices to which a thread may be bound.
auto set_name(std::string const &name) const -> expected< void, std::error_code >
auto set_affinity(ThreadAffinity const &affinity) const -> expected< void, std::error_code >
auto native_handle() const noexcept -> native_handle_type
auto set_priority(ThreadPriority priority) const -> expected< void, std::error_code >
auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const -> expected< void, std::error_code >
auto get_name() const -> std::optional< std::string >
ThreadByNameView(const std::string &name)
auto found() const noexcept -> bool
auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const -> expected< void, std::error_code >
static auto get_thread_id() -> Tid
auto get_name() const -> std::optional< std::string >
static auto get_current_priority() -> std::optional< int >
auto get_policy() const -> std::optional< SchedulingPolicy >
static auto get_current_policy() -> std::optional< SchedulingPolicy >
auto set_name(std::string const &name) const -> expected< void, std::error_code >
auto get_priority() const -> std::optional< int >
auto set_affinity(ThreadAffinity const &affinity) const -> expected< void, std::error_code >
auto get_affinity() const -> std::optional< ThreadAffinity >
auto thread_id() const noexcept -> Tid
auto set_priority(ThreadPriority priority) const -> expected< void, std::error_code >
static auto hardware_concurrency() -> unsigned int
Value-semantic wrapper for a thread scheduling priority.
Non-owning view over an externally managed std::thread.
auto get() const noexcept -> std::thread const &
auto get() noexcept -> std::thread &
Owning wrapper around std::thread with RAII join-on-destroy semantics.
auto operator=(ThreadWrapper const &) -> ThreadWrapper &=delete
auto release() noexcept -> std::thread
auto operator=(ThreadWrapper &&other) noexcept -> ThreadWrapper &
operator std::thread() &&noexcept
static auto create_with_config(std::string const &name, SchedulingPolicy policy, ThreadPriority priority, F &&f, Args &&... args) -> ThreadWrapper
ThreadWrapper(std::thread &&t) noexcept
ThreadWrapper(ThreadWrapper const &)=delete
ThreadWrapper(ThreadWrapper &&other) noexcept
ThreadWrapper(F &&f, Args &&... args)
auto underlying() const noexcept -> ThreadType const &
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.
auto configure_thread(ThreadLike &thread, std::string const &name, SchedulingPolicy policy, ThreadPriority priority) -> expected< void, std::error_code >
auto read_priority(pthread_t handle) -> std::optional< int >
auto read_scheduling_policy(pthread_t handle) -> std::optional< SchedulingPolicy >
auto apply_affinity(pthread_t handle, ThreadAffinity const &affinity) -> expected< void, std::error_code >
auto apply_priority(pthread_t handle, ThreadPriority priority) -> expected< void, std::error_code >
auto read_name(pthread_t handle) -> std::optional< std::string >
auto apply_scheduling_policy(pthread_t handle, SchedulingPolicy policy, ThreadPriority priority) -> expected< void, std::error_code >
auto apply_name(pthread_t handle, std::string const &name) -> expected< void, std::error_code >
auto read_affinity(pthread_t handle) -> std::optional< ThreadAffinity >
SchedulingPolicy
Enumeration of available thread scheduling policies.
ThreadWrapperView JThreadWrapperView
ThreadWrapper JThreadWrapper
Owning wrapper around std::jthread with cooperative cancellation (C++20).
Scheduling policies, thread priority, and CPU affinity types.
Tag type selecting non-owning (pointer) storage in ThreadStorage.
Tag type selecting owning (value) storage in ThreadStorage.