NUMA++ 0.11.0
Loading...
Searching...
No Matches
thread.hpp
Go to the documentation of this file.
1/**
2 * @file
3 * @ingroup numapp
4 * @copyright ESO 2024 - European Southern Observatory
5 *
6 * @brief Contains declarations for numapp thread utilities
7 *
8 * @defgroup numapp_thread Thread APIs
9 * @ingroup numapp
10 * @brief NUMA++ thread APIs
11 */
12#ifndef NUMAPP_THREAD_HPP_
13#define NUMAPP_THREAD_HPP_
14#include <numapp/config.hpp>
15
16#include <string>
17#include <string_view>
18#include <system_error>
19#include <thread>
20#include <type_traits>
21
24
25namespace numapp {
26namespace thisThread {
27
28/**
29 * Query the thread id "TID" of the current thread.
30 *
31 * @note This is the Kernel task identifier and is unrelated to any pthread id.
32 *
33 * It is provided in NUMA++ as @c gettid() is not provided by glibc.
34 *
35 * @return callers thread ID.
36 *
37 * @manpages
38 * @manpage{gettid,2}
39 *
40 * @ingroup numapp_thread
41 */
42[[nodiscard]] pid_t GetThreadId() noexcept;
43
44/**
45 * Set thread name for current thread.
46 *
47 * @param thread_name Name of thread. Maximum length (`thread_name.length()`) is 15.
48 * @param[out] ec out-parameter for error reporting.
49 * - std::errc::result_out_of_range if thread_name is longer than 16 characters.
50 * - other errors propagated from underlying prctl call.
51 *
52 * @sa GetThreadName()
53 * @ingroup numapp_thread
54 */
55void SetThreadName(std::string_view thread_name, std::error_code& ec) noexcept;
56
57/**
58 * Set thread name for current thread (throwing version).
59 *
60 * @param thread_name Name of thread. Maximum length (`thread_name.length()`) is 15.
61 * @throws std::system_error on errors.
62 *
63 * @sa GetThreadName()
64 * @ingroup numapp_thread
65 */
66void SetThreadName(std::string_view thread_name);
67
68/**
69 * Get name of current thread.
70 *
71 * @param[out] ec out-parameter for error reporting.
72 *
73 * @return string containing current thread name.
74 * @return empty string if error occurs (error is reported in @a ec).
75 * @throws std::bad_alloc if allocation fails.
76 * @sa SetThreadName()
77 * @ingroup numapp_thread
78 */
79[[nodiscard]] std::string GetThreadName(std::error_code& ec) noexcept;
80
81/**
82 * Get name of current thread (throwing version).
83 *
84 * @return string containing current thread name.
85 * @return empty string if error occurs (error is reported in @a ec).
86 * @throws std::bad_alloc if allocation fails.
87 * @sa SetThreadName()
88 * @ingroup numapp_thread
89 */
90[[nodiscard]] std::string GetThreadName();
91
92} // namespace thisThread
93
94/**
95 * @name Makes a std::thread or std::jthread with provided NUMA policies
96 * @anchor make_thread
97 * Create a named thread with optional CPU affinity, scheduler and memory policies.
98 *
99 * Function has the following effects in this thread `(P)arent` and new thread `(C)hild`:
100 *
101 * - <sup>(P)</sup>Create std::thread with an unspecified *thread-setup* function as target.
102 * - <sup>(P)</sup>Wait for child to be created and complete setup.
103 * - <sup>(C)</sup>Function will set thread name and apply policies.
104 * - <sup>(C)</sup>Function will apply memory policy to thread stack memory which will move
105 * physical pages if necessary. This is done with strict MemPolicyFlag such that if moving pages
106 * fails the thread creation will fail.
107 * - <sup>(C)</sup>Function signals parent thread with success/failure.
108 * - <sup>(C)</sup>If setup was successful invoke @c func with @a args, and in case of std::jthread
109 * the associated `std::stop_token` if func is invocable with it, otherwise return.
110 * - <sup>(P)</sup>Wake on signal from child:
111 * - If setup of new thread failed it will join with thread and throw @c std:system_error
112 * containing error.
113 * - On success it returns thread.
114 *
115 * @param thread_name Name of thread, Must be maximum 16 characters (15 + '\0').
116 * @param policies The NUMA policies to apply.
117 * @param func Callable invoked in new thread.
118 * @param args Arguments for func which will be decay-copied. Use `std::reference_wrapper` to pass
119 * references (remember: caller must ensure the life-time of references objects).
120 * @throws std::system_error if new thread failed to apply policies.
121 *
122 * @tparam Func MoveConstructible function to invoke in new thread.
123 * @tparam Args MoveConstructible arguments to decay copy and call Func with in new thread.
124 *
125 * Minimum application example with a `main()` function:
126 * @include makeThreadExample.cpp
127 *
128 * Example function that create pinned thread with local NUMA node memory policy:
129 * @include makeThreadExample2.cpp
130 *
131 * Similar to first example, but uses member function instead:
132 * @include makeThreadExample3.cpp
133 *
134 * @ingroup numapp_thread
135 */
136/// @{
137/**
138 * Primary overload accepting string-view for @c thread_name.
139 *
140 * See @ref make_thread "MakeThread group" for detailed documentation.
141 * @ingroup numapp_thread
142 */
143template <class Func, class... Args>
144[[nodiscard]] std::thread MakeThread(std::string_view thread_name,
145 NumaPolicies const& policies,
146 Func&& func,
147 Args&&... args) {
148 static_assert(std::is_constructible_v<std::decay_t<Func>, Func>,
149 "ill formed - Func must be MoveConstructible");
150 static_assert((std::is_constructible_v<std::decay_t<Args>, Args> && ...),
151 "ill formed - Args must be MoveConstructible");
152 static_assert(std::is_invocable_v<std::decay_t<Func>, std::decay_t<Args>...>,
153 "ill formed - Func must be invocable with Args");
154
155 auto trampoline = detail::ThreadInitializer(thread_name, policies);
156
157 // note: let std::thread forward arguments rather than capturing in lambda as it would make
158 // lambda non-copyable/movable if combinations of args is non-copyable/movable.
159 auto thr = std::thread(
160 [&trampoline, func = std::forward<Func>(func)](auto&&... args) mutable {
161 if (trampoline.Initialize() != std::error_code()) {
162 // Abort
163 return;
164 }
165 // Finally run
166 std::invoke(std::move(func), std::move(args)...);
167 },
168 std::forward<Args>(args)...);
169 // Wait for thread to start to complete
170 auto result = trampoline.Wait();
171 if (result.code()) {
172 // Thread failed to set name or apply policy
173 thr.join();
174 throw std::system_error(result);
175 }
176 return thr;
177}
178
179/**
180 * Compatibility overload accepting null terminated C string for thread_name.
181 *
182 * See @ref make_thread "MakeThread group" for detailed documentation.
183 * @ingroup numapp_thread
184 */
185template <class Func, class... Args>
186[[nodiscard]] std::thread
187MakeThread(char const* thread_name, NumaPolicies const& policies, Func&& func, Args&&... args) {
188 return MakeThread(std::string_view(thread_name),
189 policies,
190 std::forward<Func>(func),
191 std::forward<Args>(args)...);
192}
193
194#ifdef __cpp_lib_jthread // C++20
195
196/**
197 * Create std::jthread with specified named and NUMA policies.
198 *
199 * @note Like `std::jthread` it can optionally accept a `std::stop_token` as the first
200 * argument.
201 *
202 * @tparam Func MoveConstructible function to invoke in new thread. Func may optionally take
203 * `std::stop_token` as first argument.
204 * @tparam Args MoveConstructible arguments to decay copy and call Func with in new thread.
205 *
206 * See @ref make_thread "MakeThread group" for detailed documentation.
207 * @ingroup numapp_thread
208 */
209template <class Func, class... Args>
210[[nodiscard]] std::jthread MakeJthread(std::string_view thread_name,
211 NumaPolicies const& policies,
212 Func&& func,
213 Args&&... args) {
214 static_assert(std::is_constructible_v<std::decay_t<Func>, Func>,
215 "ill formed - Func must be MoveConstructible");
216 static_assert((std::is_constructible_v<std::decay_t<Args>, Args> && ...),
217 "ill formed - Args must be MoveConstructible");
218 static_assert(
219 std::is_invocable_v<std::decay_t<Func>, std::decay_t<Args>...> ||
220 std::is_invocable_v<std::decay_t<Func>, std::stop_token, std::decay_t<Args>...>,
221 "ill formed - Func must be invocable with Args, with our without stop token");
222
223 auto trampoline = detail::ThreadInitializer(thread_name, policies);
224 // note: let std::thread forward arguments rather than capturing in lambda as it would make
225 // lambda non-copyable/movable if combinations of args is non-copyable/movable.
226 auto thr = std::jthread(
227 [&trampoline, func = std::forward<Func>(func)](std::stop_token const& token,
228 auto&&... args) mutable {
229 if (trampoline.Initialize() != std::error_code()) {
230 // Abort
231 return;
232 }
233 // Finally run, with or without std::stop_token
234 if constexpr (std::is_invocable_v<std::decay_t<Func>,
235 std::stop_token,
236 std::decay_t<Args>...>) {
237 std::invoke(std::move(func), token, std::move(args)...);
238 } else {
239 std::invoke(std::move(func), std::move(args)...);
240 }
241 },
242 std::forward<Args>(args)...);
243
244 // Wait for thread to start to complete
245 auto result = trampoline.Wait();
246 if (result.code()) {
247 // Thread failed to set name or apply policy
248 thr.join();
249 throw std::system_error(result);
250 }
251
252 return thr;
253}
254
255#endif
256/// @}
257
258} // namespace numapp
259#endif // NUMAPP_THREAD_HPP_
Combines the the available NUMA policy types in one object.
NUMA++ configuration.
pid_t GetThreadId() noexcept
Query the thread id "TID" of the current thread.
Definition thread.cpp:27
std::string GetThreadName(std::error_code &ec) noexcept
Get name of current thread.
Definition thread.cpp:57
std::thread MakeThread(std::string_view thread_name, NumaPolicies const &policies, Func &&func, Args &&... args)
Primary overload accepting string-view for thread_name.
Definition thread.hpp:144
void SetThreadName(std::string_view thread_name, std::error_code &ec) noexcept
Set thread name for current thread.
Definition thread.cpp:32
Contains declarations for NumaPolicies.