libhatchet
Loading...
Searching...
No Matches
hxthread.hpp
Go to the documentation of this file.
1#pragma once
2// SPDX-FileCopyrightText: © 2017-2026 Adrian Johnston.
3// SPDX-License-Identifier: MIT
4// This file is licensed under the MIT license found in the LICENSE.md file.
5
10
11#include "libhatchet.h"
12
13// HX_USE_MACROS_WITH_MODULE allows including macros alongside the module.
14#if HX_USE_MACROS_WITH_MODULE
15#error Header does not provide macros alone.
16#endif
17#if (HX_USE_THREADS) != 0 && (HX_USE_THREADS) != 1 && (HX_USE_THREADS) != 11
18#error HX_USE_THREADS must be 0, 1 or 11. 11 is for using <threads.h>.
19#endif
20
21#if (HX_USE_THREADS) == 11
22#include <threads.h>
23#elif HX_USE_THREADS
24#include <pthread.h>
25#endif
26
27#include "hxutility.h"
28
29HX_NS_BEGIN_
30
36template<typename T_>
38 static_assert(sizeof(T_) <= sizeof(void*), "hxthread_local: sizeof(T) must be <= sizeof(void*)");
39
40public:
43#if (HX_USE_THREADS) == 11
44 const int code_ = ::tss_create(&m_key_, 0);
45 hxassert_always(code_ == thrd_success, "tss_create %d", code_); (void)code_;
46#elif HX_USE_THREADS
47 const int code_ = ::pthread_key_create(&m_key_, 0);
48 hxassert_always(code_ == 0, "pthread_key_create %s", ::strerror(code_)); (void)code_;
49#else
50 m_value_ = T_();
51#endif
52 }
53
56#if (HX_USE_THREADS) == 11
57 ::tss_delete(m_key_);
58#elif HX_USE_THREADS
59 ::pthread_key_delete(m_key_);
60#endif
61 }
62
64 hxinline hxattr_flatten operator T_(void) {
65#if (HX_USE_THREADS) == 11
66 return (T_)(intptr_t)::tss_get(m_key_); // NOLINT(google-readability-casting)
67#elif HX_USE_THREADS
68 return (T_)(intptr_t)::pthread_getspecific(m_key_); // NOLINT(google-readability-casting)
69#else
70 return m_value_;
71#endif
72 }
73
76#if (HX_USE_THREADS) == 11
77 const int code_ = ::tss_set(m_key_, (void*)(intptr_t)local_); // NOLINT(google-readability-casting)
78 hxassert_hard(code_ == thrd_success, "tss_set %d", code_); (void)code_;
79#elif HX_USE_THREADS
80 const int code_ = ::pthread_setspecific(m_key_, (void*)(intptr_t)local_); // NOLINT(google-readability-casting)
81 hxassert_hard(code_ == 0, "pthread_setspecific %s", ::strerror(code_)); (void)code_;
82#else
83 m_value_ = local_;
84#endif
85 }
86
87private:
88 explicit hxthread_local(const hxthread_local&) = delete;
89 hxthread_local& operator=(const hxthread_local&) = delete;
90
91 // No allocation is being made with which to return a pointer to.
92 void operator&(void) const = delete;
93 void operator&(void) = delete;
94
95#if (HX_USE_THREADS) == 11
96 ::tss_t m_key_;
97#elif HX_USE_THREADS
98 ::pthread_key_t m_key_;
99#else
100 T_ m_value_;
101#endif
102};
103
106hxinline size_t hxthread_id(void) {
107#if (HX_USE_THREADS) == 11
108#if defined(_WIN32)
109 return static_cast<size_t>(::thrd_current()._Tid);
110#else
111 return static_cast<size_t>(::thrd_current());
112#endif
113#elif HX_USE_THREADS
114 return static_cast<size_t>(::pthread_self());
115#else
116 return 0; // Single threaded.
117#endif
118}
119
120// The remaining classes are only available when threading is enabled. Emulating
121// pthreads is a little too nutty because it has a range of valid implementations.
122#if HX_USE_THREADS
123
126class hxmutex {
127public:
131#if (HX_USE_THREADS) == 11
132 const int code_ = ::mtx_init(&m_mutex_, mtx_plain);
133 hxassert_always(code_ == thrd_success, "mtx_init %d", code_); (void)code_;
134#else
135 const int code_ = ::pthread_mutex_init(&m_mutex_, 0);
136 hxassert_always(code_ == 0, "pthread_mutex_init %s", ::strerror(code_)); (void)code_;
137#endif
138 }
139
142#if (HX_USE_THREADS) == 11
143 ::mtx_destroy(&m_mutex_);
144#else
145 const int code_ = ::pthread_mutex_destroy(&m_mutex_);
146 hxassertmsg(code_ == 0, "pthread_mutex_destroy %s", ::strerror(code_)); (void)code_;
147#endif
148 }
149
154#if (HX_USE_THREADS) == 11
155 const int code_ = ::mtx_lock(&m_mutex_);
156 hxassertmsg(code_ == thrd_success, "mtx_lock %d", code_);
157 return code_ == thrd_success;
158#else
159 const int code_ = ::pthread_mutex_lock(&m_mutex_);
160 hxassertmsg(code_ == 0 || code_ == EAGAIN, "pthread_mutex_lock %s", ::strerror(code_));
161 return code_ == 0;
162#endif
163 }
164
168 hxinline bool unlock(void) {
169#if (HX_USE_THREADS) == 11
170 const int code_ = ::mtx_unlock(&m_mutex_);
171 hxassertmsg(code_ == thrd_success, "mtx_unlock %d", code_);
172 return code_ == thrd_success;
173#else
174 const int code_ = ::pthread_mutex_unlock(&m_mutex_);
175 hxassertmsg(code_ == 0, "pthread_mutex_unlock %s", ::strerror(code_));
176 return code_ == 0;
177#endif
178 }
179
181#if (HX_USE_THREADS) == 11
182 hxinline ::mtx_t* native_handle(void) { return &m_mutex_; }
183#else
184 hxinline ::pthread_mutex_t* native_handle(void) { return &m_mutex_; }
185#endif
186
187private:
188 hxmutex(const hxmutex&) = delete;
189 hxmutex& operator=(const hxmutex&) = delete;
190
191#if (HX_USE_THREADS) == 11
192 ::mtx_t m_mutex_;
193#else
194 ::pthread_mutex_t m_mutex_;
195#endif
196};
197
201public:
204 hxinline hxunique_lock(hxmutex& mtx_, bool defer_lock_=false)
205 : m_mutex_(mtx_), m_owns_(false) {
206 if(!defer_lock_) {
207 this->lock();
208 }
209 }
210
212 if(m_owns_) {
213 this->unlock();
214 }
215 }
216
217 hxinline void lock(void) {
218 if(!m_owns_) {
219 m_owns_ = m_mutex_.lock();
220 }
221 }
222
223 hxinline void unlock(void) {
224 if(m_owns_) {
225 m_mutex_.unlock();
226 m_owns_ = false;
227 }
228 }
229
231 hxinline bool owns_lock(void) const { return m_owns_; }
232
234 hxinline hxmutex& mutex(void) { return m_mutex_; }
235
236private:
237 hxunique_lock(const hxunique_lock&) = delete;
238 hxunique_lock& operator=(const hxunique_lock&) = delete;
239 hxmutex& m_mutex_;
240 bool m_owns_;
241};
242
247public:
250#if (HX_USE_THREADS) == 11
251 const int code_ = ::cnd_init(&m_cond_);
252 hxassert_always(code_ == thrd_success, "cnd_init %d", code_); (void)code_;
253#else
254 const int code_ = ::pthread_cond_init(&m_cond_, 0);
255 hxassert_always(code_ == 0, "pthread_cond_init %s", ::strerror(code_)); (void)code_;
256#endif
257 }
258
261#if (HX_USE_THREADS) == 11
262 ::cnd_destroy(&m_cond_);
263#else
264 const int code_ = ::pthread_cond_destroy(&m_cond_);
265 hxassertmsg(code_ == 0, "pthread_cond_destroy %s", ::strerror(code_)); (void)code_;
266#endif
267 }
268
274#if (HX_USE_THREADS) == 11
275 const int code_ = ::cnd_wait(&m_cond_, mutex_.native_handle());
276 hxassertmsg(code_ == thrd_success, "cnd_wait %d", code_);
277 return code_ == thrd_success;
278#else
279 const int code_ = ::pthread_cond_wait(&m_cond_, mutex_.native_handle());
280 hxassertmsg(code_ == 0, "pthread_cond_wait %s", ::strerror(code_));
281 return code_ == 0;
282#endif
283 }
284
290 return this->wait(lock_.mutex());
291 }
292
296 template<typename callable_t_>
297 hxinline void wait(hxunique_lock& lock_, callable_t_&& callable_) {
298 while(!callable_()) {
299 // Failure is undefined as per the standard.
300 const bool wait_result_ = this->wait(lock_);
301 hxassertmsg(wait_result_, "wait"); (void)wait_result_;
302 }
303 }
304
306 hxinline bool notify_one(void) {
307#if (HX_USE_THREADS) == 11
308 const int code_ = ::cnd_signal(&m_cond_);
309 hxassertmsg(code_ == thrd_success, "cnd_signal %d", code_);
310 return code_ == thrd_success;
311#else
312 const int code_ = ::pthread_cond_signal(&m_cond_);
313 hxassertmsg(code_ == 0, "pthread_cond_signal %s", ::strerror(code_));
314 return code_ == 0;
315#endif
316 }
317
319 hxinline bool notify_all(void) {
320#if (HX_USE_THREADS) == 11
321 const int code_ = ::cnd_broadcast(&m_cond_);
322 hxassertmsg(code_ == thrd_success, "cnd_broadcast %d", code_);
323 return code_ == thrd_success;
324#else
325 const int code_ = ::pthread_cond_broadcast(&m_cond_);
326 hxassertmsg(code_ == 0, "pthread_cond_broadcast %s", ::strerror(code_));
327 return code_ == 0;
328#endif
329 }
330
332#if (HX_USE_THREADS) == 11
333 hxinline ::cnd_t* native_handle(void) { return &m_cond_; }
334#else
335 hxinline ::pthread_cond_t* native_handle(void) { return &m_cond_; }
336#endif
337
338private:
340 hxcondition_variable& operator=(const hxcondition_variable&) = delete;
341
342#if (HX_USE_THREADS) == 11
343 ::cnd_t m_cond_;
344#else
345 ::pthread_cond_t m_cond_;
346#endif
347};
348
351class hxthread {
352public:
353#if (HX_USE_THREADS) == 11
356 using return_t = int;
357#else
358 using return_t = void*;
359#endif
360
362 hxinline hxthread(void) : m_thread_(), m_joinable_(false) { }
363
370 template<typename parameter_t_>
371 hxinline explicit hxthread(return_t (*entry_point_)(parameter_t_*), parameter_t_* parameter_)
372 : hxthread() {
373 this->start(entry_point_, parameter_);
374 }
375
378 hxassert_hard(!this->joinable(), "thread_still_running");
379 }
380
387 template<typename parameter_t_>
388 hxinline void start(return_t (*entry_point_)(parameter_t_*), parameter_t_* parameter_) {
389 hxassertmsg(!this->joinable(), "thread_still_running");
390
391 // Stay on the right side of the C++ standard by avoiding assumptions
392 // about pointer representations. The parameter is being reinterpreted
393 // twice instead of cast once and reinterpreted back.
394 static_assert(sizeof(void*) == sizeof(parameter_t_*), "Incompatible pointer sizes");
395
396 void* reinterpreted_parameter_ = hxnull;
397 ::memcpy(&reinterpreted_parameter_, &parameter_, sizeof(void*)); // NOLINT(bugprone-bitwise-pointer-cast)
398#if (HX_USE_THREADS) == 11
399 const int code_ = ::thrd_create(&m_thread_,
400 reinterpret_cast<entry_point_function_t_>(entry_point_), reinterpreted_parameter_);
401 hxassert_always(code_ == thrd_success, "thrd_create %d", code_); (void)code_;
402#else
403 const int code_ = ::pthread_create(&m_thread_, 0,
404 reinterpret_cast<entry_point_function_t_>(entry_point_), reinterpreted_parameter_);
405 hxassert_always(code_ == 0, "pthread_create %s", ::strerror(code_)); (void)code_;
406#endif
407 m_joinable_ = true;
408 }
409
412 hxinline hxattr_nodiscard bool joinable(void) const { return m_joinable_; }
413
415 hxinline void join(void) {
416 hxassertmsg(this->joinable(), "thread_not_running");
417#if (HX_USE_THREADS) == 11
418 const int code_ = ::thrd_join(m_thread_, hxnull);
419 hxassert_always(code_ == thrd_success, "thrd_join %d", code_);
420 (void)code_;
421#else
422 const int code_ = ::pthread_join(m_thread_, 0);
423 hxassert_always(code_ == 0, "pthread_join %s", ::strerror(code_));
424 (void)code_;
425#endif
426 m_joinable_ = false;
427 }
428
429private:
430 using entry_point_function_t_ = return_t (*)(void*);
431
432 hxthread(const hxthread&) = delete;
433 hxthread& operator=(const hxthread&) = delete;
434
435#if (HX_USE_THREADS) == 11
436 ::thrd_t m_thread_;
437#else
438 ::pthread_t m_thread_;
439#endif
440 bool m_joinable_;
441};
442
443#endif // HX_USE_THREADS
444HX_NS_END_
hxcondition_variable(void)
Constructs and initializes the condition variable.
Definition hxthread.hpp:249
bool notify_one(void)
Notifies one waiting thread. Returns true on success, false otherwise.
Definition hxthread.hpp:306
bool notify_all(void)
Notifies all waiting threads. Returns true on success, false otherwise.
Definition hxthread.hpp:319
inline::cnd_t * native_handle(void)
Returns a pointer to the native condition variable handle.
Definition hxthread.hpp:333
~hxcondition_variable(void)
Destroys the condition variable if valid.
Definition hxthread.hpp:260
bool wait(hxunique_lock &lock_)
Overload: Waits using a hxunique_lock.
Definition hxthread.hpp:289
void wait(hxunique_lock &lock_, callable_t_ &&callable_)
Waits until the predicate returns true.
Definition hxthread.hpp:297
bool wait(hxmutex &mutex_)
Waits for the condition variable to be notified.
Definition hxthread.hpp:273
hxmutex - std::mutex style wrapper for the configured threading backend.
Definition hxthread.hpp:126
bool lock(void)
Locks the mutex.
Definition hxthread.hpp:153
bool unlock(void)
Unlocks the mutex.
Definition hxthread.hpp:168
hxmutex(void)
Constructs a mutex and initializes it.
Definition hxthread.hpp:130
~hxmutex(void)
Destroys the mutex.
Definition hxthread.hpp:141
inline::mtx_t * native_handle(void)
Returns a pointer to the native mutex handle.
Definition hxthread.hpp:182
hxthread_local - Provides thread-local storage for an integer or a pointer.
Definition hxthread.hpp:37
hxthread_local(void)
Construct to 0 or null.
Definition hxthread.hpp:42
void operator=(T local_)
Sets the thread-local value. This is a form of "mutable when const.".
Definition hxthread.hpp:75
~hxthread_local()
Frees resources.
Definition hxthread.hpp:55
hxthread - std::thread style thread wrapper for the configured backend.
Definition hxthread.hpp:351
hxthread(void)
Default constructor. Thread is not started.
Definition hxthread.hpp:362
hxthread(return_t(*entry_point_)(parameter_t_ *), parameter_t_ *parameter_)
Constructs and starts a thread with the given function and argument.
Definition hxthread.hpp:371
void start(return_t(*entry_point_)(parameter_t_ *), parameter_t_ *parameter_)
Starts a thread with the given function and argument.
Definition hxthread.hpp:388
bool joinable(void) const
Returns true if the thread has been started and not yet joined.
Definition hxthread.hpp:412
int return_t
The return type of a thread entry point as required by the configured backend.
Definition hxthread.hpp:356
~hxthread(void)
Destructor. Asserts that the thread was stopped correctly.
Definition hxthread.hpp:377
void join(void)
Joins the thread. Blocks until the thread finishes.
Definition hxthread.hpp:415
hxunique_lock - std::unique_lock style RAII-style unique lock for hxmutex.
Definition hxthread.hpp:200
bool owns_lock(void) const
Returns true if the lock owns the mutex.
Definition hxthread.hpp:231
void lock(void)
Locks the mutex if not already locked.
Definition hxthread.hpp:217
hxunique_lock(hxmutex &mtx_, bool defer_lock_=false)
Constructs with an option to defer locking.
Definition hxthread.hpp:204
hxmutex & mutex(void)
Returns a reference to the associated mutex.
Definition hxthread.hpp:234
~hxunique_lock(void)
Unlocks the mutex if owned.
Definition hxthread.hpp:211
void unlock(void)
Unlocks the mutex if owned.
Definition hxthread.hpp:223
#define hxinline
hxinline - Force a function to be inlined into its callers.
Definition hxsettings.h:120
#define hxattr_nodiscard
hxattr_nodiscard - Indicates the caller should not discard the return value.
Definition hxsettings.h:89
#define hxattr_flatten
hxattr_flatten - Inline every call inside a function's body into it, recursively, regardless of the c...
Definition hxsettings.h:82
size_t hxthread_id(void)
hxthread_id - Returns the current thread ID.
Definition hxthread.hpp:106
A few utility functions and most standard C++ meta programming functions.
Provides core macros, memory management and feature detection.
#define hxassertmsg(x_,...)
hxassertmsg(bool x, ...) - Logs an error and terminates execution if x is false.
Definition libhatchet.h:101
#define hxnull
hxnull - The null pointer value for a given pointer type represented by the numeric constant 0.
Definition libhatchet.h:277
#define hxassert_always(x_,...)
hxassert_always(bool x, ...) - Logs an error and terminates execution if x is false.
Definition libhatchet.h:121
#define hxassert_hard(x_,...)
hxassert_hard(bool x, ...) - Logs an error and terminates execution if x is false.
Definition libhatchet.h:111