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