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