ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
thread_backend.hpp
Go to the documentation of this file.
1#pragma once
2
8#include "../expected.hpp"
10#ifdef _WIN32
11# include "unique_handle.hpp"
12#endif
13#include <condition_variable>
14#include <functional>
15#include <memory>
16#include <mutex>
17#include <optional>
18#include <string>
19#include <thread>
20#include <tuple>
21
22#ifdef _WIN32
23# include "windows_api.hpp"
24# include <libloaderapi.h>
25#else
26# include <sys/prctl.h>
27# include <sys/resource.h>
28# include <sys/syscall.h>
29# include <unistd.h>
30#endif
31
33{
35{
36public:
37 void
39 {
40 {
41 std::lock_guard<std::mutex> lock(mutex_);
42 tid_ = tid;
43 ready_ = true;
44 }
45 ready_condition_.notify_all();
46 }
47
48 [[nodiscard]] auto
50 {
51 std::unique_lock<std::mutex> lock(mutex_);
52 ready_condition_.wait(lock, [this] { return ready_; });
53 return tid_;
54 }
55
56private:
57 std::mutex mutex_;
58 std::condition_variable ready_condition_;
59 native_thread_id tid_{};
60 bool ready_{ false };
61};
62
63[[nodiscard]] inline auto
65{
66#ifdef _WIN32
67 return GetCurrentThreadId();
68#else
69 return static_cast<pid_t>(syscall(SYS_gettid));
70#endif
71}
72
75{
76};
80{
81};
82
83template <typename ThreadType, typename OwnershipTag>
85
102template <typename ThreadType>
103class thread_storage<ThreadType, owning_tag>
104{
105protected:
106 thread_storage() = default;
107
108 [[nodiscard]] auto
109 underlying() noexcept -> ThreadType&
110 {
111 return thread_;
112 }
113 [[nodiscard]] auto
114 underlying() const noexcept -> ThreadType const&
115 {
116 return thread_;
117 }
118
119 ThreadType thread_;
120};
121
142template <typename ThreadType>
144{
145protected:
146 thread_storage() = default;
147 explicit thread_storage(ThreadType& t) : external_thread_(&t) {}
148
149 [[nodiscard]] auto
150 underlying() noexcept -> ThreadType&
151 {
152 return *external_thread_;
153 }
154 [[nodiscard]] auto
155 underlying() const noexcept -> ThreadType const&
156 {
157 return *external_thread_;
158 }
159
160 ThreadType* external_thread_ = nullptr; // non-owning
161};
162
163template <typename ThreadLike>
164inline auto
165configure_thread(ThreadLike& thread, std::string const& name, native_scheduling_policy policy,
167{
168 auto named = thread.set_name(name);
169 if (!named)
170 return unexpected(named.error());
171 return thread.set_scheduling_policy(policy, priority);
172}
173
174template <typename ThreadLike>
175inline auto
177{
178 if (config.name)
179 {
180 auto named = thread.set_name(*config.name);
181 if (!named)
182 return unexpected(named.error());
183 }
184 if (config.scheduling)
185 {
186 auto scheduled = thread.configure(*config.scheduling);
187 if (!scheduled)
188 return unexpected(scheduled.error());
189 }
190 if (config.affinity.has_value())
191 return thread.set_affinity(*config.affinity);
192 return {};
193}
254template <typename ThreadType, typename OwnershipTag = detail::owning_tag>
255class basic_thread_backend : protected detail::thread_storage<ThreadType, OwnershipTag>
256{
257public:
258 using native_handle_type = typename ThreadType::native_handle_type;
259 using id = typename ThreadType::id;
260
262 explicit basic_thread_backend(ThreadType& t) : detail::thread_storage<ThreadType, OwnershipTag>(t) {}
264 : detail::thread_storage<ThreadType, OwnershipTag>(t), native_id_(tid)
265 {
266 }
267 virtual ~basic_thread_backend() = default;
268
269 // Thread management
270 void
272 {
273 if (underlying().joinable())
274 {
275 underlying().join();
276 native_id_ = {};
277 }
278 }
279
280 void
282 {
283 if (underlying().joinable())
284 {
285 underlying().detach();
286 native_id_ = {};
287 }
288 }
289
290 [[nodiscard]] auto
291 joinable() const noexcept -> bool
292 {
293 return underlying().joinable();
294 }
295 [[nodiscard]] auto
296 get_id() const noexcept -> id
297 {
298 return underlying().get_id();
299 }
300 [[nodiscard]] auto
302 {
303 if (!joinable())
304 return native_handle_type{};
305 return underlying().native_handle();
306 }
307
308 [[nodiscard]] auto
309 set_name(std::string const& name) -> expected<void, std::error_code>
310 {
311 if (!joinable())
312 return no_such_thread();
313 return detail::apply_name(native_handle(), name);
314 }
315
316 [[nodiscard]] auto
317 get_name() const -> expected<std::string, std::error_code>
318 {
319 if (!joinable())
320 return no_such_thread();
321 return detail::read_name(const_cast<basic_thread_backend*>(this)->native_handle());
322 }
323
324 [[nodiscard]] auto
326 {
327 if (!joinable())
328 return no_such_thread();
329#ifdef _WIN32
330 return detail::apply_priority(native_handle(), priority);
331#else
333 detail::native_schedule::posix_nice(priority.value()));
334#endif
335 }
336
337 [[nodiscard]] auto
340 {
341 if (!joinable())
342 return no_such_thread();
343 return detail::apply_scheduling_policy(native_handle(), policy, priority);
344 }
345
346 [[nodiscard]] auto
353
354 [[nodiscard]] auto
356 {
357 if (!joinable())
358 return no_such_thread();
359#ifdef _WIN32
360 return detail::apply_nice_value(native_handle(), nice_value);
361#else
362 if (native_id_ <= 0)
363 return unexpected(std::make_error_code(std::errc::operation_not_supported));
364 return detail::apply_nice_value(native_id_, nice_value);
365#endif
366 }
367
368 [[nodiscard]] auto
369 get_nice_value() const -> expected<int, std::error_code>
370 {
371 if (!joinable())
372 return no_such_thread();
374 }
375
376 [[nodiscard]] auto
378 {
379 return detail::configure_thread(*this, config);
380 }
381
382 [[nodiscard]] auto
384 {
385 if (!joinable())
386 return no_such_thread();
388 }
389
390 [[nodiscard]] auto
392 {
393 if (!joinable())
394 return no_such_thread();
395 return detail::read_affinity(const_cast<basic_thread_backend*>(this)->native_handle());
396 }
397
398 [[nodiscard]] auto
399 native_id() const noexcept -> native_thread_id
400 {
401 return native_id_;
402 }
403
404protected:
405 [[nodiscard]] static auto
407 {
408 return unexpected(std::make_error_code(std::errc::no_such_process));
409 }
410
411 void
413 {
414 native_id_ = tid;
415 }
416
417 using detail::thread_storage<ThreadType, OwnershipTag>::underlying;
418 using detail::thread_storage<ThreadType, OwnershipTag>::thread_storage;
419
421};
422
460class thread_backend : public basic_thread_backend<std::thread, detail::owning_tag>
461{
462public:
463 thread_backend() = default;
464
465 [[nodiscard]] auto
466 get() noexcept -> std::thread&
467 {
468 return this->underlying();
469 }
470
471 [[nodiscard]] auto
472 get() const noexcept -> std::thread const&
473 {
474 return this->underlying();
475 }
476
477 // Construct by taking ownership of an existing std::thread (move)
478 thread_backend(std::thread&& t) noexcept
479 {
480 this->underlying() = std::move(t);
481 }
482
483 thread_backend(std::thread&& t, native_thread_id tid) noexcept
484 {
485 this->underlying() = std::move(t);
486 this->set_native_id(tid);
487 }
488
489 template <typename F, typename... Args>
490 explicit thread_backend(F&& f, Args&&... args) : basic_thread_backend()
491 {
492 auto identity = std::make_shared<thread_identity_state>();
493 using function_type = std::decay_t<F>;
494 auto arguments = std::make_tuple(std::forward<Args>(args)...);
495 this->underlying() = std::thread(
496 [identity, function = function_type(std::forward<F>(f)), arguments = std::move(arguments)]() mutable
497 {
498 identity->publish(current_native_thread_id());
499 std::apply([&function](auto&&... stored)
500 { std::invoke(std::move(function), std::forward<decltype(stored)>(stored)...); },
501 std::move(arguments));
502 });
503 this->set_native_id(identity->wait());
504 }
505
507 auto operator=(thread_backend const&) -> thread_backend& = delete;
508
510 {
511 this->underlying() = std::move(other.underlying());
512 this->set_native_id(other.native_id());
513 other.set_native_id({});
514 }
515
516 auto
518 {
519 if (this != &other)
520 {
521 if (this->underlying().joinable())
522 {
523 this->underlying().join();
524 }
525 this->underlying() = std::move(other.underlying());
526 this->set_native_id(other.native_id());
527 other.set_native_id({});
528 }
529 return *this;
530 }
531
533 {
534 if (this->underlying().joinable())
535 {
536 this->underlying().join();
537 }
538 }
539
540 // Ownership transfer to std::thread for APIs that take plain std::thread
541 auto
542 release() noexcept -> std::thread
543 {
544 auto result = std::move(this->underlying());
545 this->set_native_id({});
546 return result;
547 }
548
549 explicit
550 operator std::thread() && noexcept
551 {
552 return release();
553 }
554
555 // Factory methods
556 template <typename F, typename... Args>
557 static auto
558 create_with_config(std::string const& name, native_scheduling_policy policy, native_thread_priority priority, F&& f,
559 Args&&... args) -> thread_backend
560 {
561 thread_backend wrapper(std::forward<F>(f), std::forward<Args>(args)...);
562 (void)wrapper.set_name(name);
563 (void)wrapper.set_scheduling_policy(policy, priority);
564 return wrapper;
565 }
566
567 template <typename F, typename... Args>
568 static auto
569 create_with_config(native_thread_config const& config, F&& f, Args&&... args) -> thread_backend
570 {
571 thread_backend wrapper(std::forward<F>(f), std::forward<Args>(args)...);
572 (void)wrapper.configure(config);
573 return wrapper;
574 }
575};
576
597class thread_view_backend : public basic_thread_backend<std::thread, detail::non_owning_tag>
598{
599public:
601
603 : basic_thread_backend<std::thread, detail::non_owning_tag>(t, tid)
604 {
605 }
606
607 // Non-owning access to the underlying std::thread
608 auto
609 get() noexcept -> std::thread&
610 {
611 return this->underlying();
612 }
613 [[nodiscard]] auto
614 get() const noexcept -> std::thread const&
615 {
616 return this->underlying();
617 }
618};
619
646{
647public:
648#ifdef _WIN32
649 using native_handle_type = HANDLE;
650#else
651 using native_handle_type = pthread_t;
652#endif
653
655 {
656 bind_current_thread_handle();
657 }
658
659 explicit thread_info(native_thread_id tid) : tid_(tid) {}
660
661 [[nodiscard]] auto
662 thread_id() const noexcept -> native_thread_id
663 {
664 return tid_;
665 }
666
667 [[nodiscard]] auto
668 set_name(std::string const& name) const -> expected<void, std::error_code>
669 {
670 if (has_native_handle())
671 return detail::apply_name(native_handle(), name);
672 return detail::apply_name(tid_, name);
673 }
674
675 [[nodiscard]] auto
676 get_name() const -> expected<std::string, std::error_code>
677 {
678 if (has_native_handle())
679 return detail::read_name(native_handle());
680 return detail::read_name(tid_);
681 }
682
683 [[nodiscard]] auto
685 {
686#ifdef _WIN32
687 if (has_native_handle())
688 return detail::apply_priority(native_handle(), priority);
689 return detail::apply_priority(tid_, priority);
690#else
691 if (has_native_handle())
692 return detail::apply_scheduling_config(native_handle(), tid_,
693 detail::native_schedule::posix_nice(priority.value()));
694 return detail::apply_scheduling_config(tid_, tid_, detail::native_schedule::posix_nice(priority.value()));
695#endif
696 }
697
698 [[nodiscard]] auto
701 {
702 if (has_native_handle())
703 return detail::apply_scheduling_policy(native_handle(), policy, priority);
704 return detail::apply_scheduling_policy(tid_, policy, priority);
705 }
706
707 [[nodiscard]] auto
709 {
710 if (has_native_handle())
711 return detail::apply_scheduling_config(native_handle(), tid_, config);
712 return detail::apply_scheduling_config(tid_, tid_, config);
713 }
714
715 [[nodiscard]] auto
717 {
718 return detail::configure_thread(*this, config);
719 }
720
721 [[nodiscard]] auto
723 {
724 if (has_native_handle())
725 return detail::apply_affinity_checked(native_handle(), affinity);
726 return detail::apply_affinity_checked(tid_, affinity);
727 }
728
729 [[nodiscard]] auto
731 {
732 if (has_native_handle())
733 return detail::read_affinity(native_handle());
734 return detail::read_affinity(tid_);
735 }
736
737 [[nodiscard]] auto
739 {
740 if (has_native_handle())
741 return detail::read_scheduling_policy(native_handle());
742 return detail::read_scheduling_policy(tid_);
743 }
744
745 [[nodiscard]] auto
746 get_priority() const -> std::optional<int>
747 {
748 if (has_native_handle())
749 return detail::read_priority(native_handle());
750 return detail::read_priority(tid_);
751 }
752
753 static auto
754 hardware_concurrency() -> unsigned int
755 {
756 return std::thread::hardware_concurrency();
757 }
758
759 static auto
761 {
762#ifdef _WIN32
763 return GetCurrentThreadId();
764#else
765 return static_cast<pid_t>(syscall(SYS_gettid));
766#endif
767 }
768
769 static auto
770 get_current_policy() -> std::optional<native_scheduling_policy>
771 {
772 return thread_info().get_policy();
773 }
774
775 static auto
776 get_current_priority() -> std::optional<int>
777 {
778 return thread_info().get_priority();
779 }
780
781private:
782 void
783 bind_current_thread_handle()
784 {
785#ifdef _WIN32
786 HANDLE real_handle = nullptr;
787 if (DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &real_handle,
788 THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION, FALSE, 0)
789 != 0)
790 {
791 native_handle_owner_ = std::make_shared<unique_handle>(real_handle);
792 native_handle_ = native_handle_owner_->get();
793 has_native_handle_ = true;
794 }
795#else
796 native_handle_ = pthread_self();
797 has_native_handle_ = true;
798#endif
799 }
800
801 [[nodiscard]] auto
802 has_native_handle() const noexcept -> bool
803 {
804 return has_native_handle_;
805 }
806
807 [[nodiscard]] auto
808 native_handle() const noexcept -> native_handle_type
809 {
810 return native_handle_;
811 }
812
813 native_thread_id tid_{};
814#ifdef _WIN32
815 native_handle_type native_handle_ = nullptr;
816 std::shared_ptr<unique_handle> native_handle_owner_;
817#else
818 native_handle_type native_handle_{};
819#endif
820 bool has_native_handle_{ false };
821};
822
823} // namespace threadschedule::detail
Polymorphic base providing common thread management operations.
auto set_nice_value(int nice_value) -> expected< void, std::error_code >
static auto no_such_thread() -> unexpected< std::error_code >
auto native_handle() noexcept -> native_handle_type
auto configure(native_thread_config const &config) -> expected< void, std::error_code >
auto set_name(std::string const &name) -> expected< void, std::error_code >
auto native_id() const noexcept -> native_thread_id
auto get_affinity() const -> expected< native_thread_affinity, std::error_code >
auto get_nice_value() const -> expected< int, std::error_code >
auto configure(native_scheduling_config const &config) -> expected< void, std::error_code >
auto set_affinity(native_thread_affinity const &affinity) -> expected< void, std::error_code >
typename ThreadType::native_handle_type native_handle_type
auto set_scheduling_policy(native_scheduling_policy policy, native_thread_priority priority) -> expected< void, std::error_code >
basic_thread_backend(ThreadType &t, native_thread_id tid)
auto set_priority(native_thread_priority priority) -> expected< void, std::error_code >
auto get_name() const -> expected< std::string, std::error_code >
void set_native_id(native_thread_id tid) noexcept
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
Owning wrapper around std::thread with RAII join-on-destroy semantics.
auto get() const noexcept -> std::thread const &
static auto create_with_config(std::string const &name, native_scheduling_policy policy, native_thread_priority priority, F &&f, Args &&... args) -> thread_backend
static auto create_with_config(native_thread_config const &config, F &&f, Args &&... args) -> thread_backend
auto get() noexcept -> std::thread &
auto operator=(thread_backend &&other) noexcept -> thread_backend &
thread_backend(std::thread &&t, native_thread_id tid) noexcept
thread_backend(thread_backend &&other) noexcept
thread_backend(std::thread &&t) noexcept
auto operator=(thread_backend const &) -> thread_backend &=delete
thread_backend(thread_backend const &)=delete
auto release() noexcept -> std::thread
Lightweight handle for querying and controlling a specific OS thread.
static auto get_thread_id() -> native_thread_id
auto set_affinity(native_thread_affinity const &affinity) const -> expected< void, std::error_code >
static auto hardware_concurrency() -> unsigned int
static auto get_current_priority() -> std::optional< int >
auto set_scheduling_policy(native_scheduling_policy policy, native_thread_priority priority) const -> expected< void, std::error_code >
auto get_name() const -> expected< std::string, std::error_code >
auto get_policy() const -> std::optional< native_scheduling_policy >
auto configure(native_scheduling_config const &config) const -> expected< void, std::error_code >
auto set_priority(native_thread_priority priority) const -> expected< void, std::error_code >
auto configure(native_thread_config const &config) const -> expected< void, std::error_code >
auto get_priority() const -> std::optional< int >
auto get_affinity() const -> expected< native_thread_affinity, std::error_code >
static auto get_current_policy() -> std::optional< native_scheduling_policy >
auto set_name(std::string const &name) const -> expected< void, std::error_code >
auto thread_id() const noexcept -> native_thread_id
Non-owning view over an externally managed std::thread.
thread_view_backend(std::thread &t, native_thread_id tid)
auto get() const noexcept -> std::thread const &
auto get() noexcept -> std::thread &
Strongly-typed nice value in the POSIX range $[-20, 19]$.
Owning thread wrapper with result-based lifecycle/configuration API.
Definition thread.hpp:29
auto set_name(std::string const &name) -> result< void >
Set thread name.
Definition thread.hpp:183
auto configure(thread_config const &config) -> result< void >
Apply a full portable thread configuration to the running thread.
Definition thread.hpp:148
auto set_affinity(thread_affinity const &affinity) -> result< void >
Apply CPU affinity set.
Definition thread.hpp:197
constexpr auto posix_nice(int nice_value) noexcept -> native_scheduling_config
Definition native.hpp:313
Aggregates multiple thread_registry_backend instances into a single queryable view.
auto read_effective_nice(NativeHandle handle, native_thread_id tid) -> expected< int, std::error_code >
Definition native.hpp:847
native_scheduling_policy
Enumeration of available thread scheduling policies.
Definition native.hpp:85
@ other
Standard round-robin time-sharing.
auto configure_thread(ThreadLike &thread, std::string const &name, native_scheduling_policy policy, native_thread_priority priority) -> expected< void, std::error_code >
auto apply_affinity_checked(NativeHandle handle, native_thread_affinity const &affinity) -> expected< void, std::error_code >
Definition native.hpp:790
auto current_native_thread_id() noexcept -> native_thread_id
auto apply_scheduling_config(NativeHandle handle, native_thread_id tid, native_scheduling_config const &config) -> expected< void, std::error_code >
Definition native.hpp:814
expected< T, std::error_code > result
Standard result type used by public APIs.
Definition result.hpp:22
Scheduling policies, thread priority, and CPU affinity types.
Tag type selecting non-owning (pointer) storage in thread_storage.
Tag type selecting owning (value) storage in thread_storage.