Fixposition SDK 0.0.0-heads/main-0-gde8559b
Collection of c++ libraries and apps for use with Fixposition products on Linux
Loading...
Searching...
No Matches
thread.hpp
Go to the documentation of this file.
1/**
2 * \verbatim
3 * ___ ___
4 * \ \ / /
5 * \ \/ / Copyright (c) Fixposition AG (www.fixposition.com) and contributors
6 * / /\ \ License: see the LICENSE file
7 * /__/ \__\
8 * \endverbatim
9 *
10 * @file
11 * @brief Fixposition SDK: Thread helpers
12 *
13 * @page FPSDK_COMMON_THREAD Thread helpers
14 *
15 * **API**: fpsdk_common/thread.hpp and fpsdk::common::thread
16 *
17 */
18#ifndef __FPSDK_COMMON_THREAD_HPP__
19#define __FPSDK_COMMON_THREAD_HPP__
20
21/* LIBC/STL */
22#include <atomic>
23#include <condition_variable>
24#include <cstdint>
25#include <functional>
26#include <memory>
27#include <mutex>
28#include <string>
29#include <thread>
30
31/* EXTERNAL */
32
33/* PACKAGE */
34
35namespace fpsdk {
36namespace common {
37/**
38 * @brief Thread helpers
39 */
40namespace thread {
41/* ****************************************************************************************************************** */
42
43/**
44 * @brief Thread sleep result
45 */
46enum class WaitRes
47{
48 WOKEN, //!< Thread was woken up (sleep interrupted), or semaphore was taken (wait interrupted)
49 TIMEOUT, //!< Sleep or wait timeout has expired (no wakeup, no interrupt)
50};
51
52/**
53 * @brief A binary semaphore, useful for thread synchronisation
54 *
55 * The default state is "taken", that is, WaitFor() and WaitUntil() block (sleep) until the semaphore is "given" using
56 * Notify(). See the example in the Thread class documentation below. Note that this semaphore can be "taken"
57 * immediately if it has been "given" before. That is, if Notify() was called before WaitFor() or WaitUntil(), the
58 * latter return immediately on the first call. Subsequent calls wait until the next Notify(), be it before or while
59 * waiting.
60 */
62{
63 public:
65
66 /**
67 * @brief Notify ("signal", "give")
68 */
69 void Notify();
70
71 /**
72 * @brief Wait (take), with timeout
73 *
74 * @param[in] millis Number of [ms] to sleep, must be > 0
75 *
76 * @returns WaitRes::WOKEN if taken (within time limit), WaitRes::TIMEOUT if the timeout has expired
77 */
78 WaitRes WaitFor(const uint32_t millis);
79
80 /**
81 * @brief Wait (take), with timout aligned to a period
82 *
83 * This blocks (sleeps) until the start of the next millis period is reached (or the semaphore has been taken). The
84 * period is aligned to the system clock. For example, with a period of 500ms it would timeout at t+0.0s, t+0.5s,
85 * t+1.0s, t+1.5s, etc. If the calculated wait duration is less then the given minimal wait duration, it waits
86 * longer than one period in order to achieve the minimal wait duration and still timeout on the start of a period.
87 *
88 * Note that this works on the actual system clock. In ROS replay scenario this may not behave as expected as the
89 * fake ROS system clock may be faster or slower than the actual system clock.
90 *
91 * @param[in] period Period duration [ms], must be > 0
92 * @param[in] min_sleep Minimal sleep duration [ms], must be < period
93 *
94 * @returns WaitRes::WOKEN if taken (within time limit), WaitRes::TIMEOUT if the timeout has expired or period was 0
95 */
96 WaitRes WaitUntil(const uint32_t period, const uint32_t min_sleep = 0);
97
98 private:
99 std::mutex mutex_; //!< Mutex
100 std::condition_variable cond_; //!< Condition
101 bool pend_ = false; //!< Pending
102};
103
104// ---------------------------------------------------------------------------------------------------------------------
105
106/**
107 * @brief Helper class for handling threads
108 *
109 * Example:
110 *
111 * @code{.cpp}
112 * class Example {
113 * public:
114 *
115 * Example() :
116 * // We're only interested in the first ThreadFunc argument
117 * thread_ { "worker", std::bind(&Example::Worker, this, std::placeholders::_1) }
118 * { }
119 *
120 * void Run() {
121 *
122 * if (!thread_.Start()) {
123 * throw "...";
124 * }
125 *
126 * int n = 0;
127 * while (thread_.GetStatus() == Thread::Status::RUNNING) {
128 *
129 * sleep(500);
130 * thread_.Wakeup() // = BinarySemaphore::Notify()
131 *
132 * sleep(2000);
133 * thread_.Wakeup() // = BinarySemaphore::Notify()
134 *
135 * n++;
136 * if (n >= 10) {
137 * thread_.Stop();
138 * }
139 * }
140 * }
141 *
142 * private:
143 *
144 * // Note that the first argument is a reference to the thread itself.
145 * bool Worker(Thread& thread) {
146 * while (!thread.ShouldAbort()) {
147 * if (thread.Sleep(123) == WaitRes::WOKEN) { // = BinarySemaphore::SleepFor(1000)
148 * // We have been woken up
149 * } else {
150 * // Timeout expired
151 * }
152 * }
153 * return true;
154 * }
155 *
156 * Thread thread_;
157 * };
158 *
159 * @endcode
160 */
162{
163 public:
164 /**
165 * @brief Thread main function
166 *
167 * @param[in] thread Reference to the thread handle
168 * @param[in] arg User argument passed in Thread contstructor
169 *
170 * @returns true if the thread happily ended (Thread::GetStatus() will return STOPPED) or false if the thread
171 * sadly ended (Thread::GetStatus() will return FAILED)
172 */
173 using ThreadFunc = std::function<bool(Thread& thread, void* arg)>;
174
175 /**
176 * @brief Thread pepare function
177 *
178 * @param[in] arg User argument passed in Thread contstructor
179 */
180 using PrepFunc = std::function<void(void* arg)>;
181
182 /**
183 * @brief Thread cleanup function
184 *
185 * @param[in] arg User argument passed in Thread contstructor
186 */
187 using CleanFunc = std::function<void(void* arg)>;
188
189 /**
190 * @brief Constructor
191 *
192 * @param[in] name Name of the thread, for debugging
193 * @param[in] func The thread function
194 * @param[in] arg Optional thread function user argument
195 * @param[in] prep Optional prepare function, called before the thread function
196 * @param[in] clean Optional cleanup function, called after the threadd stopped (or crashed)
197 *
198 * @note Constructing an instance delibarately does not automatically start the thread!
199 */
200 Thread(const std::string& name, ThreadFunc func, void* arg = nullptr, PrepFunc prep = nullptr,
201 CleanFunc clean = nullptr);
202
203 /**
204 * @brief Destructor, blocks until thread has stopped
205 */
207
208 // ----- Common methods -----
209
210 /**
211 * @brief Get thread name
212 *
213 * @returns the thread name
214 */
215 const std::string& GetName();
216
217 // -----------------------------------------------------------------------------------------------------------------
218
219 /**
220 * @name Main (controlling) thread methods
221 * @{
222 */
223
224 /**
225 * @brief Start the thread
226 *
227 * @note In case a thread is re-Start()ed, it must have been Stop()ed previously, even if it ended by itself. Apps
228 * that repeatedly Start() and Stop() a thread must keep track of this themselves.
229 *
230 * @param[in] try_catch Run user-supplied thread function in a try ... catch block
231 *
232 * @returns true if the thread was started, false otherwise
233 */
234 bool Start(const bool try_catch = true);
235
236 /**
237 * @brief Stop the thread
238 *
239 * @returns true if the thread was stopped, false otherwise
240 */
241 bool Stop();
242
243 /**
244 * @brief Wakup a sleeping thread
245 */
246 void Wakeup();
247
248 /**
249 * @brief Thread status
250 */
252 {
253 STOPPED, //!< Stopped (not Start()ed, or properly and happily Stop()ped)
254 RUNNING, //!< Running (Start(ed) and happily running)
255 FAILED, //!< Failed (was Start()ed, but crashed due to an exception)
256 };
257
258 /**
259 * @brief Stringify status
260 *
261 * @param[in] status The status
262 *
263 * @returns a stringification of the status
264 */
265 static const char* StatusStr(const Status status);
266
267 /**
268 * @brief Check thread status
269 *
270 * @returns the thread status
271 */
273
274 //@}
275
276 // -----------------------------------------------------------------------------------------------------------------
277
278 /**
279 * @name Worker thread methods
280 * @{
281 */
282
283 /**
284 * @brief Sleep until timeout or woken up
285 *
286 * @param[in] millis Number of [ms] to sleep
287 *
288 * @returns WaitRes::WOKEN if the thread has been woken up, WaitRes::TIMEOUT if the timeout has expired
289 */
290 WaitRes Sleep(const uint32_t millis);
291
292 /**
293 * @brief Sleep until next period start or woken up
294 *
295 * See BinarySemaphore::WaitUntil() for a detailed explanation.
296 *
297 * @param[in] period Period duration [ms], must be > 0
298 * @param[in] min_sleep Minimal sleep duration [ms], must be < period
299 *
300 * @returns WaitRes::WOKEN if the thread has been woken up, WaitRes::TIMEOUT if the timeout has expired or period
301 * was 0
302 */
303 WaitRes SleepUntil(const uint32_t period, const uint32_t min_sleep = 0);
304
305 /**
306 * @brief Check if we should abort
307 */
309
310 //@}
311
312 // -----------------------------------------------------------------------------------------------------------------
313
314 private:
315 // clang-format off
316 std::string name_; //!< Thread name
317 std::unique_ptr<std::thread> thread_; //!< Thread handle
318 ThreadFunc func_; //!< Thread function
319 void *arg_; //!< Thread function argument
320 PrepFunc prep_; //!< Thread prepare function
321 CleanFunc clean_; //!< Thread cleanup function
322 std::atomic<bool> abort_ = false; //!< Abort signal
323 std::atomic<bool> started_ = false; //!< Thread was started
324 std::atomic<Status> status_ = Status::STOPPED; //!< Thread status
325 BinarySemaphore sem_; //!< Semaphore
326 // clang-format on
327 void _Thread(const bool try_catch); //!< Wrapper function to call the user thread function
328};
329
330// ---------------------------------------------------------------------------------------------------------------------
331
332/**
333 * @brief Set thread name
334 *
335 * Sets the thread name, which shows in htop etc.
336 *
337 * @param[in] name The thread name (1-6 characters, longer \c name is clipped at 6 chars)
338 */
339void SetThreadName(const std::string& name);
340
341/**
342 * @brief Get numeric thread ID
343 *
344 * Like std::this_thread::get_id(), but numeric (and therefore printf()-able, etc.).
345 */
346std::size_t ThisThreadId();
347
348/* ****************************************************************************************************************** */
349} // namespace thread
350} // namespace common
351} // namespace fpsdk
352#endif // __FPSDK_COMMON_THREAD_HPP__
A binary semaphore, useful for thread synchronisation.
Definition thread.hpp:62
WaitRes WaitUntil(const uint32_t period, const uint32_t min_sleep=0)
Wait (take), with timout aligned to a period.
WaitRes WaitFor(const uint32_t millis)
Wait (take), with timeout.
void Notify()
Notify ("signal", "give")
Helper class for handling threads.
Definition thread.hpp:162
Status GetStatus() const
Check thread status.
Thread(const std::string &name, ThreadFunc func, void *arg=nullptr, PrepFunc prep=nullptr, CleanFunc clean=nullptr)
Constructor.
WaitRes SleepUntil(const uint32_t period, const uint32_t min_sleep=0)
Sleep until next period start or woken up.
bool ShouldAbort()
Check if we should abort.
void Wakeup()
Wakup a sleeping thread.
@ RUNNING
Running (Start(ed) and happily running)
Definition thread.hpp:254
@ FAILED
Failed (was Start()ed, but crashed due to an exception)
Definition thread.hpp:255
@ STOPPED
Stopped (not Start()ed, or properly and happily Stop()ped)
Definition thread.hpp:253
std::function< bool(Thread &thread, void *arg)> ThreadFunc
Thread main function.
Definition thread.hpp:173
std::function< void(void *arg)> CleanFunc
Thread cleanup function.
Definition thread.hpp:187
static const char * StatusStr(const Status status)
Stringify status.
bool Stop()
Stop the thread.
std::function< void(void *arg)> PrepFunc
Thread pepare function.
Definition thread.hpp:180
~Thread()
Destructor, blocks until thread has stopped.
WaitRes Sleep(const uint32_t millis)
Sleep until timeout or woken up.
const std::string & GetName()
Get thread name.
bool Start(const bool try_catch=true)
Start the thread.
WaitRes
Thread sleep result.
Definition thread.hpp:47
@ TIMEOUT
Sleep or wait timeout has expired (no wakeup, no interrupt)
@ WOKEN
Thread was woken up (sleep interrupted), or semaphore was taken (wait interrupted)
void SetThreadName(const std::string &name)
Set thread name.
std::size_t ThisThreadId()
Get numeric thread ID.
Fixposition SDK.