• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. _module-pw_thread:
2
3=========
4pw_thread
5=========
6The ``pw_thread`` module contains utilities for thread creation and thread
7execution.
8
9.. Warning::
10  This module is still under construction, the API is not yet stable.
11
12---------------
13Thread Sleeping
14---------------
15C++
16===
17.. cpp:function:: void pw::this_thread::sleep_for(chrono::SystemClock::duration sleep_duration)
18
19   Blocks the execution of the current thread for at least the specified
20   duration. This function may block for longer due to scheduling or resource
21   contention delays.
22
23   A sleep duration of 0 will at minimum yield, meaning it will provide a hint
24   to the implementation to reschedule the execution of threads, allowing other
25   threads to run.
26
27   **Precondition:** This can only be called from a thread, meaning the
28   scheduler is running.
29
30.. cpp:function:: void pw::this_thread::sleep_until(chrono::SystemClock::time_point wakeup_time)
31
32   Blocks the execution of the current thread until at least the specified
33   time has been reached. This function may block for longer due to scheduling
34   or resource contention delays.
35
36   A sleep deadline in the past up to the current time will at minimum yield
37   meaning it will provide a hint to the implementation to reschedule the
38   execution of threads, allowing other threads to run.
39
40   **Precondition:** This can only be called from a thread, meaning the
41   scheduler is running.
42
43Examples in C++
44---------------
45.. code-block:: cpp
46
47  #include <chrono>
48
49  #include "pw_chrono/system_clock.h"
50  #include "pw_thread/sleep.h"
51
52  using std::literals::chrono_literals::ms;
53
54  void FunctionInvokedByThread() {
55    pw::this_thread::sleep_for(42ms);
56  }
57
58  void AnotherFunctionInvokedByThread() {
59    pw::this_thread::sleep_until(pw::chrono::SystemClock::now() + 42ms);
60  }
61
62C
63=
64.. cpp:function:: void pw_this_thread_SleepFor(pw_chrono_SystemClock_Duration sleep_duration)
65
66   Invokes ``pw::this_thread::sleep_until(sleep_duration)``.
67
68.. cpp:function:: void pw_this_thread_SleepUntil(pw_chrono_SystemClock_TimePoint wakeup_time)
69
70   Invokes ``pw::this_thread::sleep_until(wakeup_time)``.
71
72
73---------------
74Thread Yielding
75---------------
76C++
77===
78.. cpp:function:: void pw::this_thread::yield() noexcept
79
80   Provides a hint to the implementation to reschedule the execution of threads,
81   allowing other threads to run.
82
83   The exact behavior of this function depends on the implementation, in
84   particular on the mechanics of the OS scheduler in use and the state of the
85   system.
86
87   **Precondition:** This can only be called from a thread, meaning the
88   scheduler is running.
89
90Example in C++
91---------------
92.. code-block:: cpp
93
94  #include "pw_thread/yield.h"
95
96  void FunctionInvokedByThread() {
97    pw::this_thread::yield();
98  }
99
100C
101=
102.. cpp:function:: void pw_this_thread_Yield(void)
103
104   Invokes ``pw::this_thread::yield()``.
105
106---------------------
107Thread Identification
108---------------------
109The class ``pw::thread::Id`` is a lightweight, trivially copyable class that
110serves as a unique identifier of Thread objects.
111
112Instances of this class may also hold the special distinct value that does
113not represent any thread. Once a thread has finished, the value of its
114Thread::id may be reused by another thread.
115
116This class is designed for use as key in associative containers, both ordered
117and unordered.
118
119Although the current API is similar to C++11 STL
120`std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_, it is
121missing the required hashing and streaming operators and may diverge further in
122the future.
123
124A thread's identification (``pw::thread::Id``) can be acquired only in C++ in
125one of two ways:
126
1271) Using the ``pw::thread::Thread`` handle's ``pw::thread::Id get_id() const``
128   method.
1292) While executing the thread using
130   ``pw::thread::Id pw::this_thread::get_id() noexcept``.
131
132.. cpp:function:: pw::thread::Id pw::this_thread::get_id() noexcept
133
134   This is thread safe, not IRQ safe. It is implementation defined whether this
135   is safe before the scheduler has started.
136
137
138Example
139=======
140.. code-block:: cpp
141
142  #include "pw_thread/id.h"
143
144  void FunctionInvokedByThread() {
145    const pw::thread::Id my_id = pw::this_thread::get_id();
146  }
147
148---------------
149Thread Creation
150---------------
151The class ``pw::thread::Thread`` can represent a single thread of execution.
152Threads allow multiple functions to execute concurrently.
153
154The Thread's API is C++11 STL
155`std::thread <https://en.cppreference.com/w/cpp/thread/thread>`_ like, meaning
156the object is effectively a thread handle and not an object which contains the
157thread's context. Unlike ``std::thread``, the API requires
158``pw::thread::Options`` as an argument and is limited to only work with
159``pw::thread::ThreadCore`` objects and functions which match the
160``pw::thread::Thread::ThreadRoutine`` signature.
161
162We recognize that the C++11's STL ``std::thread``` API has some drawbacks where
163it is easy to forget to join or detach the thread handle. Because of this, we
164offer helper wrappers like the ``pw::thread::DetachedThread``. Soon we will
165extend this by also adding a ``pw::thread::JoiningThread`` helper wrapper which
166will also have a lighter weight C++20 ``std::jthread`` like cooperative
167cancellation contract to make joining safer and easier.
168
169Threads may begin execution immediately upon construction of the associated
170thread object (pending any OS scheduling delays), starting at the top-level
171function provided as a constructor argument. The return value of the
172top-level function is ignored. The top-level function may communicate its
173return value by modifying shared variables (which may require
174synchronization, see :ref:`module-pw_sync`)
175
176Thread objects may also be in the state that does not represent any thread
177(after default construction, move from, detach, or join), and a thread of
178execution may be not associated with any thread objects (after detach).
179
180No two Thread objects may represent the same thread of execution; Thread is
181not CopyConstructible or CopyAssignable, although it is MoveConstructible and
182MoveAssignable.
183
184.. list-table::
185
186  * - *Supported on*
187    - *Backend module*
188  * - FreeRTOS
189    - :ref:`module-pw_thread_freertos`
190  * - ThreadX
191    - :ref:`module-pw_thread_threadx`
192  * - embOS
193    - :ref:`module-pw_thread_embos`
194  * - STL
195    - :ref:`module-pw_thread_stl`
196  * - Zephyr
197    - Planned
198  * - CMSIS-RTOS API v2 & RTX5
199    - Planned
200
201Module Configuration Options
202============================
203The following configurations can be adjusted via compile-time configuration of
204this module, see the
205:ref:`module documentation <module-structure-compile-time-configuration>` for
206more details.
207
208.. c:macro:: PW_THREAD_CONFIG_LOG_LEVEL
209
210  The log level to use for this module. Logs below this level are omitted.
211
212Options
213=======
214The ``pw::thread::Options`` contains the parameters or attributes needed for a
215thread to start.
216
217Pigweed does not generalize options, instead we strive to give you full control
218where we provide helpers to do this.
219
220Options are backend specific and ergo the generic base class cannot be
221directly instantiated.
222
223The attributes which can be set through the options are backend specific
224but may contain things like the thread name, priority, scheduling policy,
225core/processor affinity, and/or an optional reference to a pre-allocated
226Context (the collection of memory allocations needed for a thread to run).
227
228Options shall NOT permit starting as detached, this must be done explicitly
229through the Thread API.
230
231Options must not contain any memory needed for a thread to run (TCB,
232stack, etc.). The Options may be deleted or re-used immediately after
233starting a thread.
234
235Please see the thread creation backend documentation for how their Options work.
236
237Portable Thread Creation
238========================
239Due to the fact that ``pw::thread::Options`` cannot be created in portable code,
240some extra work must be done in order to permit portable thread creation.
241Namely, a reference to the portable ``pw::thread::Options`` base class interface
242must be provided through a header or extern which points to an instantiation in
243non-portable code.
244
245This can be most easily done through a facade and set of backends. This approach
246can be powerful; enabling multithreaded unit/integration testing which can run
247on both the host and on a device with the device's exact thread options.
248
249Alternatively, it can also be be injected at build time by instantiating backend
250specific build rule which share the same common portable source file(s) but
251select backend specific source files and/or dependencies which provide the
252non-portable option instantiations.
253
254As an example, let's say we want to create a thread on the host and on a device
255running FreeRTOS. They could use a facade which contains a ``threads.h`` header
256with the following contents:
257
258.. code-block:: cpp
259
260  // Contents of my_app/threads.h
261  #pragma once
262
263  #include "pw_thread/options.h"
264
265  namespace my_app {
266
267  const pw::thread::Options& HellowWorldThreadOptions();
268
269  }  // namespace my_app
270
271This could then be backed by two different backend implementations based on
272the thread backend. For example for the STL the backend's ``stl_threads.cc``
273source file may look something like:
274
275.. code-block:: cpp
276
277  // Contents of my_app/stl_threads.cc
278  #include "my_app/threads.h"
279  #include "pw_thread_stl/options.h"
280
281  namespace my_app {
282
283  const pw::thread::Options& HelloWorldThreadOptions() {
284    static constexpr auto options = pw::thread::stl::Options();
285    return options;
286  }
287
288  }  // namespace my_app
289
290While for FreeRTOS the backend's ``freertos_threads.cc`` source file may look
291something like:
292
293.. code-block:: cpp
294
295  // Contents of my_app/freertos_threads.cc
296  #include "FreeRTOS.h"
297  #include "my_app/threads.h"
298  #include "pw_thread_freertos/context.h"
299  #include "pw_thread_freertos/options.h"
300  #include "task.h"
301
302  namespace my_app {
303
304  StaticContextWithStack<kHelloWorldStackWords> hello_world_thread_context;
305  const pw::thread::Options& HelloWorldThreadOptions() {
306    static constexpr auto options =
307        pw::thread::freertos::Options()
308            .set_name("HelloWorld")
309            .set_static_context(hello_world_thread_context)
310            .set_priority(kHelloWorldThreadPriority);
311    return options;
312  }
313
314  }  // namespace my_app
315
316
317Detaching & Joining
318===================
319The ``Thread::detach()`` API is always available, to let you separate the
320thread of execution from the thread object, allowing execution to continue
321independently.
322
323The joining API, more specifically ``Thread::join()``, is conditionally
324available depending on the selected backend for thread creation and how it is
325configured. The backend is responsible for providing the
326``PW_THREAD_JOINING_ENABLED`` macro through
327``pw_thread_backend/thread_native.h``. This ensures that any users which include
328``pw_thread/thread.h`` can use this macro if needed.
329
330Please see the selected thread creation backend documentation for how to
331enable joining if it's not already enabled by default.
332
333.. Warning::
334  A constructed ``pw::thread::Thread`` which represents a thread of execution
335  must be EITHER detached or joined, else the destructor will assert!
336
337DetachedThread
338==============
339To make it slightly easier and cleaner to spawn detached threads without having
340to worry about thread handles, a wrapper ``DetachedThread()`` function is
341provided which creates a ``Thread`` and immediately detaches it. For example
342instead of:
343
344.. code-block:: cpp
345
346  Thread(options, foo).detach();
347
348You can instead use this helper wrapper to:
349
350.. code-block:: cpp
351
352   DetachedThread(options, foo);
353
354The arguments are directly forwarded to the Thread constructor and ergo exactly
355match the Thread constuctor arguments for creating a thread of execution.
356
357
358ThreadRoutine & ThreadCore
359==========================
360Threads must either be invoked through a
361``pw::thread::Thread::ThreadRoutine``` style function or implement the
362``pw::thread::ThreadCore`` interface.
363
364.. code-block:: cpp
365
366  namespace pw::thread {
367
368  // This function may return.
369  using Thread::ThreadRoutine = void (*)(void* arg);
370
371  class ThreadCore {
372   public:
373    virtual ~ThreadCore() = default;
374
375    // The public API to start a ThreadCore, note that this may return.
376    void Start() { Run(); }
377
378   private:
379    // This function may return.
380    virtual void Run() = 0;
381  };
382
383  }  // namespace pw::thread;
384
385
386To use the ``pw::thread::Thread::ThreadRoutine``, your function must have the
387following signature:
388
389.. code-block:: cpp
390
391  void example_thread_entry_function(void *arg);
392
393
394To invoke a member method of a class a static lambda closure can be used
395to ensure the dispatching closure is not destructed before the thread is
396done executing. For example:
397
398.. code-block:: cpp
399
400  class Foo {
401   public:
402    void DoBar() {}
403  };
404  Foo foo;
405
406  static auto invoke_foo_do_bar = [](void *void_foo_ptr) {
407      //  If needed, additional arguments could be set here.
408      static_cast<Foo*>(void_foo_ptr)->DoBar();
409  };
410
411  // Now use the lambda closure as the thread entry, passing the foo's
412  // this as the argument.
413  Thread thread(options, invoke_foo_do_bar, &foo);
414  thread.detach();
415
416
417Alternatively, the aforementioned ``pw::thread::ThreadCore`` interface can be
418be implemented by an object by overriding the private
419``void ThreadCore::Run();`` method. This makes it easier to create a thread, as
420a static lambda closure or function is not needed to dispatch to a member
421function without arguments. For example:
422
423.. code-block:: cpp
424
425  class Foo : public ThreadCore {
426   private:
427    void Run() override {}
428  };
429  Foo foo;
430
431  // Now create the thread, using foo directly.
432  Thread(options, foo).detach();
433
434.. Warning::
435  Because the thread may start after the pw::Thread creation, an object which
436  implements the ThreadCore MUST meet or exceed the lifetime of its thread of
437  execution!
438
439-----------------------
440pw_snapshot integration
441-----------------------
442``pw_thread`` provides some light, optional integration with pw_snapshot through
443helper functions for populating a ``pw::thread::Thread`` proto. Some of these
444are directly integrated into the RTOS thread backends to simplify the thread
445state capturing for snapshots.
446
447SnapshotStack()
448===============
449The ``SnapshotStack()`` helper captures stack metadata (stack pointer and
450bounds) into a ``pw::thread::Thread`` proto. After the stack bounds are
451captured, execution is passed off to the thread stack collection callback to
452capture a backtrace or stack dump. Note that this function does NOT capture the
453thread name: that metadata is only required in cases where a stack overflow or
454underflow is detected.
455
456Python processor
457================
458Threads captured as a Thread proto message can be dumped or further analyzed
459using using ``pw_thread``'s Python module. This is directly integrated into
460pw_snapshot's processor tool to automatically provide rich thread state dumps.
461
462The ``ThreadSnapshotAnalyzer`` class may also be used directly to identify the
463currently running thread and produce symbolized thread dumps.
464
465.. Warning::
466  Snapshot integration is a work-in-progress and may see significant API
467  changes.
468