ThreadSchedule 2.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
pthread_wrapper.hpp
Go to the documentation of this file.
1#pragma once
2
7
8#include "concepts.hpp"
9#include "expected.hpp"
10#include "scheduler_policy.hpp"
11#include <atomic>
12#include <exception>
13#include <functional>
14#include <memory>
15#include <optional>
16#include <string>
17#include <tuple>
18
19#ifdef _WIN32
20#include <windows.h>
21// PThreadWrapper is not available on Windows as it's POSIX-specific
22// Users should use ThreadWrapper or JThreadWrapper instead
23#else
24#include <pthread.h>
25#endif
26
27namespace threadschedule
28{
29
30#ifndef _WIN32
55{
56 public:
57 using native_handle_type = pthread_t;
58 using id = pthread_t;
59
60 PThreadWrapper() : thread_(0), joined_(false)
61 {
62 }
63
64 template <typename F, typename... Args>
65 explicit PThreadWrapper(F&& func, Args&&... args) : thread_(0), joined_(false)
66 {
67
68 auto callable =
69 std::make_unique<std::function<void()>>([fn = std::forward<F>(func),
70 tup = std::make_tuple(std::forward<Args>(args)...)]() mutable {
71 std::apply(std::move(fn), std::move(tup));
72 });
73
74 int const result = pthread_create(&thread_, nullptr, thread_function, callable.release());
75
76 if (result != 0)
77 {
78 throw std::runtime_error("Failed to create pthread: " + std::to_string(result));
79 }
80 }
81
82 // Non-copyable
84 auto operator=(PThreadWrapper const&) -> PThreadWrapper& = delete;
85
86 // Movable
87 PThreadWrapper(PThreadWrapper&& other) noexcept : thread_(other.thread_), joined_(other.joined_.load())
88 {
89 other.thread_ = 0;
90 other.joined_.store(true);
91 }
92
93 auto operator=(PThreadWrapper&& other) noexcept -> PThreadWrapper&
94 {
95 if (this != &other)
96 {
97 if (joinable())
98 {
99 join();
100 }
101 thread_ = other.thread_;
102 joined_.store(other.joined_.load());
103 other.thread_ = 0;
104 other.joined_.store(true);
105 }
106 return *this;
107 }
108
110 {
111 if (joinable())
112 {
113 join();
114 }
115 }
116
117 // Thread management
118 void join()
119 {
120 if (joinable())
121 {
122 void* retval;
123 int const result = pthread_join(thread_, &retval);
124 if (result == 0)
125 {
126 joined_ = true;
127 }
128 }
129 }
130
131 void detach()
132 {
133 if (joinable())
134 {
135 int const result = pthread_detach(thread_);
136 if (result == 0)
137 {
138 joined_ = true;
139 }
140 }
141 }
142
143 [[nodiscard]] auto joinable() const -> bool
144 {
145 return thread_ != 0 && !joined_;
146 }
147
148 [[nodiscard]] auto get_id() const -> id
149 {
150 return thread_;
151 }
152 [[nodiscard]] auto native_handle() const -> native_handle_type
153 {
154 return thread_;
155 }
156
157 [[nodiscard]] auto set_name(std::string const& name) const -> expected<void, std::error_code>
158 {
159 return detail::apply_name(thread_, name);
160 }
161
162 [[nodiscard]] auto get_name() const -> std::optional<std::string>
163 {
164 return detail::read_name(thread_);
165 }
166
167 [[nodiscard]] auto set_priority(ThreadPriority priority) const -> expected<void, std::error_code>
168 {
169 return detail::apply_priority(thread_, priority);
170 }
171
172 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const
174 {
175 return detail::apply_scheduling_policy(thread_, policy, priority);
176 }
177
178 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) const -> expected<void, std::error_code>
179 {
180 return detail::apply_affinity(thread_, affinity);
181 }
182
183 [[nodiscard]] auto get_affinity() const -> std::optional<ThreadAffinity>
184 {
185 return detail::read_affinity(thread_);
186 }
187
188 // Cancellation support
189 [[nodiscard]] auto cancel() const -> expected<void, std::error_code>
190 {
191 if (pthread_cancel(thread_) == 0)
192 return {};
193 return unexpected(std::error_code(errno, std::generic_category()));
194 }
195
197 {
198 int const state = enabled ? PTHREAD_CANCEL_ENABLE : PTHREAD_CANCEL_DISABLE;
199 int old_state;
200 if (pthread_setcancelstate(state, &old_state) == 0)
201 return {};
202 return unexpected(std::error_code(errno, std::generic_category()));
203 }
204
205 static auto set_cancel_type(bool asynchronous) -> expected<void, std::error_code>
206 {
207 int const type = asynchronous ? PTHREAD_CANCEL_ASYNCHRONOUS : PTHREAD_CANCEL_DEFERRED;
208 int old_type;
209 if (pthread_setcanceltype(type, &old_type) == 0)
210 return {};
211 return unexpected(std::error_code(errno, std::generic_category()));
212 }
213
214 // Factory methods
215 template <typename F, typename... Args>
216 static auto create_with_config(std::string const& name, SchedulingPolicy policy, ThreadPriority priority, F&& f,
217 Args&&... args) -> PThreadWrapper
218 {
219
220 PThreadWrapper wrapper(std::forward<F>(f), std::forward<Args>(args)...);
221 (void)wrapper.set_name(name);
222 (void)wrapper.set_scheduling_policy(policy, priority);
223 return wrapper;
224 }
225
226 template <typename F, typename... Args>
227 static auto create_with_attributes(pthread_attr_t const& attr, F&& func, Args&&... args) -> PThreadWrapper
228 {
229
230 PThreadWrapper wrapper;
231 auto callable =
232 std::make_unique<std::function<void()>>([fn = std::forward<F>(func),
233 tup = std::make_tuple(std::forward<Args>(args)...)]() mutable {
234 std::apply(std::move(fn), std::move(tup));
235 });
236
237 int const result = pthread_create(&wrapper.thread_, &attr, thread_function, callable.release());
238
239 if (result != 0)
240 {
241 throw std::runtime_error("Failed to create pthread with attributes: " + std::to_string(result));
242 }
243
244 return wrapper;
245 }
246
247 private:
248 pthread_t thread_;
249 std::atomic<bool> joined_;
250
251 static auto thread_function(void* arg) -> void*
252 {
253 std::unique_ptr<std::function<void()>> func(static_cast<std::function<void()>*>(arg));
254
255 try
256 {
257 (*func)();
258 }
259 catch (...)
260 {
261 // Handle exceptions - could add logging here
262 }
263
264 return nullptr;
265 }
266};
267
281{
282 public:
284 {
285 if (pthread_attr_init(&attr_) != 0)
286 {
287 throw std::runtime_error("Failed to initialize pthread attributes");
288 }
289 }
290
292 {
293 pthread_attr_destroy(&attr_);
294 }
295
296 // Non-copyable
299
300 // Movable
301 PThreadAttributes(PThreadAttributes&& other) noexcept : attr_(other.attr_)
302 {
303 if (pthread_attr_init(&other.attr_) != 0)
304 {
305 std::terminate(); // Can't throw from move constructor
306 }
307 }
308
310 {
311 if (this != &other)
312 {
313 pthread_attr_destroy(&attr_);
314 attr_ = other.attr_;
315 if (pthread_attr_init(&other.attr_) != 0)
316 {
317 std::terminate(); // Can't throw from move assignment
318 }
319 }
320 return *this;
321 }
322
323 [[nodiscard]] auto get() const -> pthread_attr_t const&
324 {
325 return attr_;
326 }
327 auto get() -> pthread_attr_t&
328 {
329 return attr_;
330 }
331
332 // Attribute setters
333 auto set_detach_state(bool detached) -> bool
334 {
335 int const state = detached ? PTHREAD_CREATE_DETACHED : PTHREAD_CREATE_JOINABLE;
336 return pthread_attr_setdetachstate(&attr_, state) == 0;
337 }
338
339 auto set_stack_size(size_t stack_size) -> bool
340 {
341 return pthread_attr_setstacksize(&attr_, stack_size) == 0;
342 }
343
344 auto set_guard_size(size_t guard_size) -> bool
345 {
346 return pthread_attr_setguardsize(&attr_, guard_size) == 0;
347 }
348
350 {
351 int const policy_int = static_cast<int>(policy);
352 return pthread_attr_setschedpolicy(&attr_, policy_int) == 0;
353 }
354
356 {
357 sched_param param{};
358 param.sched_priority = priority.value();
359 return pthread_attr_setschedparam(&attr_, &param) == 0;
360 }
361
362 auto set_inherit_sched(bool inherit) -> bool
363 {
364 int const inherit_val = inherit ? PTHREAD_INHERIT_SCHED : PTHREAD_EXPLICIT_SCHED;
365 return pthread_attr_setinheritsched(&attr_, inherit_val) == 0;
366 }
367
368 auto set_scope(bool system_scope) -> bool
369 {
370 int const scope = system_scope ? PTHREAD_SCOPE_SYSTEM : PTHREAD_SCOPE_PROCESS;
371 return pthread_attr_setscope(&attr_, scope) == 0;
372 }
373
374 // Attribute getters
375 [[nodiscard]] auto get_detach_state() const -> std::optional<bool>
376 {
377 int state;
378 if (pthread_attr_getdetachstate(&attr_, &state) == 0)
379 {
380 return state == PTHREAD_CREATE_DETACHED;
381 }
382 return std::nullopt;
383 }
384
385 [[nodiscard]] auto get_stack_size() const -> std::optional<size_t>
386 {
387 size_t stack_size;
388 if (pthread_attr_getstacksize(&attr_, &stack_size) == 0)
389 {
390 return stack_size;
391 }
392 return std::nullopt;
393 }
394
395 [[nodiscard]] auto get_guard_size() const -> std::optional<size_t>
396 {
397 size_t guard_size;
398 if (pthread_attr_getguardsize(&attr_, &guard_size) == 0)
399 {
400 return guard_size;
401 }
402 return std::nullopt;
403 }
404
405 private:
406 pthread_attr_t attr_;
407};
408
423{
424 public:
426 {
427 if (pthread_mutex_init(&mutex_, nullptr) != 0)
428 {
429 throw std::runtime_error("Failed to initialize pthread mutex");
430 }
431 }
432
433 explicit PThreadMutex(pthread_mutexattr_t const* attr)
434 {
435 if (pthread_mutex_init(&mutex_, attr) != 0)
436 {
437 throw std::runtime_error("Failed to initialize pthread mutex with attributes");
438 }
439 }
440
442 {
443 pthread_mutex_destroy(&mutex_);
444 }
445
446 PThreadMutex(PThreadMutex const&) = delete;
447 auto operator=(PThreadMutex const&) -> PThreadMutex& = delete;
448
449 void lock()
450 {
451 if (pthread_mutex_lock(&mutex_) != 0)
452 {
453 throw std::runtime_error("Failed to lock pthread mutex");
454 }
455 }
456
457 auto try_lock() -> bool
458 {
459 return pthread_mutex_trylock(&mutex_) == 0;
460 }
461
462 void unlock()
463 {
464 if (pthread_mutex_unlock(&mutex_) != 0)
465 {
466 throw std::runtime_error("Failed to unlock pthread mutex");
467 }
468 }
469
470 auto native_handle() -> pthread_mutex_t*
471 {
472 return &mutex_;
473 }
474
475 private:
476 pthread_mutex_t mutex_;
477};
478
479template <>
480struct is_thread_like<PThreadWrapper> : std::true_type
481{
482};
483
484#endif // !_WIN32
485
486} // namespace threadschedule
auto set_scope(bool system_scope) -> bool
auto get_detach_state() const -> std::optional< bool >
auto set_stack_size(size_t stack_size) -> bool
auto operator=(PThreadAttributes const &) -> PThreadAttributes &=delete
PThreadAttributes(PThreadAttributes &&other) noexcept
auto get() -> pthread_attr_t &
auto set_guard_size(size_t guard_size) -> bool
auto set_scheduling_policy(SchedulingPolicy policy) -> bool
auto set_detach_state(bool detached) -> bool
auto operator=(PThreadAttributes &&other) noexcept -> PThreadAttributes &
auto get_stack_size() const -> std::optional< size_t >
auto get() const -> pthread_attr_t const &
auto set_inherit_sched(bool inherit) -> bool
auto get_guard_size() const -> std::optional< size_t >
auto set_scheduling_parameter(ThreadPriority priority) -> bool
PThreadAttributes(PThreadAttributes const &)=delete
PThreadMutex(PThreadMutex const &)=delete
PThreadMutex(pthread_mutexattr_t const *attr)
auto native_handle() -> pthread_mutex_t *
auto operator=(PThreadMutex const &) -> PThreadMutex &=delete
RAII wrapper around POSIX threads with a modern C++ interface.
auto native_handle() const -> native_handle_type
auto cancel() const -> expected< void, std::error_code >
auto operator=(PThreadWrapper &&other) noexcept -> PThreadWrapper &
auto get_affinity() const -> std::optional< ThreadAffinity >
auto set_affinity(ThreadAffinity const &affinity) const -> expected< void, std::error_code >
PThreadWrapper(PThreadWrapper &&other) noexcept
static auto set_cancel_state(bool enabled) -> expected< void, std::error_code >
auto get_name() const -> std::optional< std::string >
PThreadWrapper(F &&func, Args &&... args)
auto operator=(PThreadWrapper const &) -> PThreadWrapper &=delete
PThreadWrapper(PThreadWrapper const &)=delete
auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const -> expected< void, std::error_code >
static auto set_cancel_type(bool asynchronous) -> expected< void, std::error_code >
static auto create_with_config(std::string const &name, SchedulingPolicy policy, ThreadPriority priority, F &&f, Args &&... args) -> PThreadWrapper
auto set_priority(ThreadPriority priority) const -> expected< void, std::error_code >
static auto create_with_attributes(pthread_attr_t const &attr, F &&func, Args &&... args) -> PThreadWrapper
auto set_name(std::string const &name) const -> expected< void, std::error_code >
Manages a set of CPU indices to which a thread may be bound.
Value-semantic wrapper for a thread scheduling priority.
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
C++20 concepts, type traits, and SFINAE helpers for the threading library.
Polyfill for std::expected (C++23) for pre-C++23 compilers.
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.
Scheduling policies, thread priority, and CPU affinity types.
Type trait that identifies thread-like types.
Definition concepts.hpp:145