ThreadSchedule 1.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
pthread_wrapper.hpp
1#pragma once
2
3#include "concepts.hpp"
4#include "expected.hpp"
5#include "scheduler_policy.hpp"
6#include <atomic>
7#include <exception>
8#include <functional>
9#include <memory>
10#include <optional>
11#include <string>
12
13#ifdef _WIN32
14#include <windows.h>
15// PThreadWrapper is not available on Windows as it's POSIX-specific
16// Users should use ThreadWrapper or JThreadWrapper instead
17#else
18#include <pthread.h>
19#endif
20
21namespace threadschedule
22{
23
24#ifndef _WIN32
48class PThreadWrapper
49{
50 public:
51 using native_handle_type = pthread_t;
52 using id = pthread_t;
53
54 PThreadWrapper() : thread_(0), joined_(false)
55 {
56 }
57
58 template <typename F, typename... Args>
59 explicit PThreadWrapper(F&& func, Args&&... args) : thread_(0), joined_(false)
60 {
61
62 // Store the callable in a way pthread can handle
63 auto callable =
64 std::make_unique<std::function<void()>>(std::bind(std::forward<F>(func), std::forward<Args>(args)...));
65
66 int const result = pthread_create(&thread_, nullptr, thread_function, callable.release());
67
68 if (result != 0)
69 {
70 throw std::runtime_error("Failed to create pthread: " + std::to_string(result));
71 }
72 }
73
74 // Non-copyable
75 PThreadWrapper(PThreadWrapper const&) = delete;
76 auto operator=(PThreadWrapper const&) -> PThreadWrapper& = delete;
77
78 // Movable
79 PThreadWrapper(PThreadWrapper&& other) noexcept : thread_(other.thread_), joined_(other.joined_.load())
80 {
81 other.thread_ = 0;
82 other.joined_.store(true);
83 }
84
85 auto operator=(PThreadWrapper&& other) noexcept -> PThreadWrapper&
86 {
87 if (this != &other)
88 {
89 if (joinable())
90 {
91 join();
92 }
93 thread_ = other.thread_;
94 joined_.store(other.joined_.load());
95 other.thread_ = 0;
96 other.joined_.store(true);
97 }
98 return *this;
99 }
100
101 ~PThreadWrapper()
102 {
103 if (joinable())
104 {
105 join();
106 }
107 }
108
109 // Thread management
110 void join()
111 {
112 if (joinable())
113 {
114 void* retval;
115 int const result = pthread_join(thread_, &retval);
116 if (result == 0)
117 {
118 joined_ = true;
119 }
120 }
121 }
122
123 void detach()
124 {
125 if (joinable())
126 {
127 int const result = pthread_detach(thread_);
128 if (result == 0)
129 {
130 joined_ = true;
131 }
132 }
133 }
134
135 [[nodiscard]] auto joinable() const -> bool
136 {
137 return thread_ != 0 && !joined_;
138 }
139
140 [[nodiscard]] auto get_id() const -> id
141 {
142 return thread_;
143 }
144 [[nodiscard]] auto native_handle() const -> native_handle_type
145 {
146 return thread_;
147 }
148
149 // Extended pthread functionality
150 [[nodiscard]] auto set_name(std::string const& name) const -> expected<void, std::error_code>
151 {
152 if (name.length() > 15)
153 return expected<void, std::error_code>(unexpect, std::make_error_code(std::errc::invalid_argument));
154 if (pthread_setname_np(thread_, name.c_str()) == 0)
155 return {};
156 return expected<void, std::error_code>(unexpect, std::error_code(errno, std::generic_category()));
157 }
158
159 [[nodiscard]] auto get_name() const -> std::optional<std::string>
160 {
161 char name[16]; // Linux limit + 1
162 if (pthread_getname_np(thread_, name, sizeof(name)) == 0)
163 {
164 return std::string(name);
165 }
166 return std::nullopt;
167 }
168
169 [[nodiscard]] auto set_priority(ThreadPriority priority) const -> expected<void, std::error_code>
170 {
171 int const policy = SCHED_OTHER;
172 auto params_result = SchedulerParams::create_for_policy(SchedulingPolicy::OTHER, priority);
173
174 if (!params_result.has_value())
175 {
176 return unexpected(params_result.error());
177 }
178
179 if (pthread_setschedparam(thread_, policy, &params_result.value()) == 0)
180 return {};
181 return unexpected(std::error_code(errno, std::generic_category()));
182 }
183
184 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const
186 {
187 int const policy_int = static_cast<int>(policy);
188 auto params_result = SchedulerParams::create_for_policy(policy, priority);
189
190 if (!params_result.has_value())
191 {
192 return unexpected(params_result.error());
193 }
194
195 if (pthread_setschedparam(thread_, policy_int, &params_result.value()) == 0)
196 return {};
197 return unexpected(std::error_code(errno, std::generic_category()));
198 }
199
200 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) const -> expected<void, std::error_code>
201 {
202 if (pthread_setaffinity_np(thread_, sizeof(cpu_set_t), &affinity.native_handle()) == 0)
203 return {};
204 return unexpected(std::error_code(errno, std::generic_category()));
205 }
206
207 [[nodiscard]] auto get_affinity() const -> std::optional<ThreadAffinity>
208 {
209 ThreadAffinity affinity;
210 if (pthread_getaffinity_np(thread_, sizeof(cpu_set_t), const_cast<cpu_set_t*>(&affinity.native_handle())) == 0)
211 {
212 return affinity;
213 }
214 return std::nullopt;
215 }
216
217 // Cancellation support
218 [[nodiscard]] auto cancel() const -> expected<void, std::error_code>
219 {
220 if (pthread_cancel(thread_) == 0)
221 return {};
222 return unexpected(std::error_code(errno, std::generic_category()));
223 }
224
225 static auto set_cancel_state(bool enabled) -> expected<void, std::error_code>
226 {
227 int const state = enabled ? PTHREAD_CANCEL_ENABLE : PTHREAD_CANCEL_DISABLE;
228 int old_state;
229 if (pthread_setcancelstate(state, &old_state) == 0)
230 return {};
231 return unexpected(std::error_code(errno, std::generic_category()));
232 }
233
234 static auto set_cancel_type(bool asynchronous) -> expected<void, std::error_code>
235 {
236 int const type = asynchronous ? PTHREAD_CANCEL_ASYNCHRONOUS : PTHREAD_CANCEL_DEFERRED;
237 int old_type;
238 if (pthread_setcanceltype(type, &old_type) == 0)
239 return {};
240 return unexpected(std::error_code(errno, std::generic_category()));
241 }
242
243 // Factory methods
244 template <typename F, typename... Args>
245 static auto create_with_config(std::string const& name, SchedulingPolicy policy, ThreadPriority priority, F&& f,
246 Args&&... args) -> PThreadWrapper
247 {
248
249 PThreadWrapper wrapper(std::forward<F>(f), std::forward<Args>(args)...);
250 (void)wrapper.set_name(name);
251 (void)wrapper.set_scheduling_policy(policy, priority);
252 return wrapper;
253 }
254
255 template <typename F, typename... Args>
256 static auto create_with_attributes(pthread_attr_t const& attr, F&& func, Args&&... args) -> PThreadWrapper
257 {
258
259 PThreadWrapper wrapper;
260 auto callable =
261 std::make_unique<std::function<void()>>(std::bind(std::forward<F>(func), std::forward<Args>(args)...));
262
263 int const result = pthread_create(&wrapper.thread_, &attr, thread_function, callable.release());
264
265 if (result != 0)
266 {
267 throw std::runtime_error("Failed to create pthread with attributes: " + std::to_string(result));
268 }
269
270 return wrapper;
271 }
272
273 private:
274 pthread_t thread_;
275 std::atomic<bool> joined_;
276
277 static auto thread_function(void* arg) -> void*
278 {
279 std::unique_ptr<std::function<void()>> func(static_cast<std::function<void()>*>(arg));
280
281 try
282 {
283 (*func)();
284 }
285 catch (...)
286 {
287 // Handle exceptions - could add logging here
288 }
289
290 return nullptr;
291 }
292};
293
306class PThreadAttributes
307{
308 public:
309 PThreadAttributes()
310 {
311 if (pthread_attr_init(&attr_) != 0)
312 {
313 throw std::runtime_error("Failed to initialize pthread attributes");
314 }
315 }
316
317 ~PThreadAttributes()
318 {
319 pthread_attr_destroy(&attr_);
320 }
321
322 // Non-copyable
323 PThreadAttributes(PThreadAttributes const&) = delete;
324 auto operator=(PThreadAttributes const&) -> PThreadAttributes& = delete;
325
326 // Movable
327 PThreadAttributes(PThreadAttributes&& other) noexcept : attr_(other.attr_)
328 {
329 if (pthread_attr_init(&other.attr_) != 0)
330 {
331 std::terminate(); // Can't throw from move constructor
332 }
333 }
334
335 auto operator=(PThreadAttributes&& other) noexcept -> PThreadAttributes&
336 {
337 if (this != &other)
338 {
339 pthread_attr_destroy(&attr_);
340 attr_ = other.attr_;
341 if (pthread_attr_init(&other.attr_) != 0)
342 {
343 std::terminate(); // Can't throw from move assignment
344 }
345 }
346 return *this;
347 }
348
349 [[nodiscard]] auto get() const -> pthread_attr_t const&
350 {
351 return attr_;
352 }
353 auto get() -> pthread_attr_t&
354 {
355 return attr_;
356 }
357
358 // Attribute setters
359 auto set_detach_state(bool detached) -> bool
360 {
361 int const state = detached ? PTHREAD_CREATE_DETACHED : PTHREAD_CREATE_JOINABLE;
362 return pthread_attr_setdetachstate(&attr_, state) == 0;
363 }
364
365 auto set_stack_size(size_t stack_size) -> bool
366 {
367 return pthread_attr_setstacksize(&attr_, stack_size) == 0;
368 }
369
370 auto set_guard_size(size_t guard_size) -> bool
371 {
372 return pthread_attr_setguardsize(&attr_, guard_size) == 0;
373 }
374
375 auto set_scheduling_policy(SchedulingPolicy policy) -> bool
376 {
377 int const policy_int = static_cast<int>(policy);
378 return pthread_attr_setschedpolicy(&attr_, policy_int) == 0;
379 }
380
381 auto set_scheduling_parameter(ThreadPriority priority) -> bool
382 {
383 sched_param param{};
384 param.sched_priority = priority.value();
385 return pthread_attr_setschedparam(&attr_, &param) == 0;
386 }
387
388 auto set_inherit_sched(bool inherit) -> bool
389 {
390 int const inherit_val = inherit ? PTHREAD_INHERIT_SCHED : PTHREAD_EXPLICIT_SCHED;
391 return pthread_attr_setinheritsched(&attr_, inherit_val) == 0;
392 }
393
394 auto set_scope(bool system_scope) -> bool
395 {
396 int const scope = system_scope ? PTHREAD_SCOPE_SYSTEM : PTHREAD_SCOPE_PROCESS;
397 return pthread_attr_setscope(&attr_, scope) == 0;
398 }
399
400 // Attribute getters
401 [[nodiscard]] auto get_detach_state() const -> std::optional<bool>
402 {
403 int state;
404 if (pthread_attr_getdetachstate(&attr_, &state) == 0)
405 {
406 return state == PTHREAD_CREATE_DETACHED;
407 }
408 return std::nullopt;
409 }
410
411 [[nodiscard]] auto get_stack_size() const -> std::optional<size_t>
412 {
413 size_t stack_size;
414 if (pthread_attr_getstacksize(&attr_, &stack_size) == 0)
415 {
416 return stack_size;
417 }
418 return std::nullopt;
419 }
420
421 [[nodiscard]] auto get_guard_size() const -> std::optional<size_t>
422 {
423 size_t guard_size;
424 if (pthread_attr_getguardsize(&attr_, &guard_size) == 0)
425 {
426 return guard_size;
427 }
428 return std::nullopt;
429 }
430
431 private:
432 pthread_attr_t attr_;
433};
434
448class PThreadMutex
449{
450 public:
451 PThreadMutex()
452 {
453 if (pthread_mutex_init(&mutex_, nullptr) != 0)
454 {
455 throw std::runtime_error("Failed to initialize pthread mutex");
456 }
457 }
458
459 explicit PThreadMutex(pthread_mutexattr_t const* attr)
460 {
461 if (pthread_mutex_init(&mutex_, attr) != 0)
462 {
463 throw std::runtime_error("Failed to initialize pthread mutex with attributes");
464 }
465 }
466
467 ~PThreadMutex()
468 {
469 pthread_mutex_destroy(&mutex_);
470 }
471
472 PThreadMutex(PThreadMutex const&) = delete;
473 auto operator=(PThreadMutex const&) -> PThreadMutex& = delete;
474
475 void lock()
476 {
477 if (pthread_mutex_lock(&mutex_) != 0)
478 {
479 throw std::runtime_error("Failed to lock pthread mutex");
480 }
481 }
482
483 auto try_lock() -> bool
484 {
485 return pthread_mutex_trylock(&mutex_) == 0;
486 }
487
488 void unlock()
489 {
490 if (pthread_mutex_unlock(&mutex_) != 0)
491 {
492 throw std::runtime_error("Failed to unlock pthread mutex");
493 }
494 }
495
496 auto native_handle() -> pthread_mutex_t*
497 {
498 return &mutex_;
499 }
500
501 private:
502 pthread_mutex_t mutex_;
503};
504
505template <>
506struct is_thread_like<PThreadWrapper> : std::true_type
507{
508};
509
510#endif // !_WIN32
511
512} // namespace threadschedule
RAII wrapper around POSIX threads with a modern C++ interface.
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.
Type trait that identifies thread-like types.
Definition concepts.hpp:145