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