468 lines
15 KiB
ReStructuredText
468 lines
15 KiB
ReStructuredText
.. _module-pw_thread:
|
|
|
|
=========
|
|
pw_thread
|
|
=========
|
|
The ``pw_thread`` module contains utilities for thread creation and thread
|
|
execution.
|
|
|
|
.. Warning::
|
|
This module is still under construction, the API is not yet stable.
|
|
|
|
---------------
|
|
Thread Sleeping
|
|
---------------
|
|
C++
|
|
===
|
|
.. cpp:function:: void pw::this_thread::sleep_for(chrono::SystemClock::duration sleep_duration)
|
|
|
|
Blocks the execution of the current thread for at least the specified
|
|
duration. This function may block for longer due to scheduling or resource
|
|
contention delays.
|
|
|
|
A sleep duration of 0 will at minimum yield, meaning it will provide a hint
|
|
to the implementation to reschedule the execution of threads, allowing other
|
|
threads to run.
|
|
|
|
**Precondition:** This can only be called from a thread, meaning the
|
|
scheduler is running.
|
|
|
|
.. cpp:function:: void pw::this_thread::sleep_until(chrono::SystemClock::time_point wakeup_time)
|
|
|
|
Blocks the execution of the current thread until at least the specified
|
|
time has been reached. This function may block for longer due to scheduling
|
|
or resource contention delays.
|
|
|
|
A sleep deadline in the past up to the current time will at minimum yield
|
|
meaning it will provide a hint to the implementation to reschedule the
|
|
execution of threads, allowing other threads to run.
|
|
|
|
**Precondition:** This can only be called from a thread, meaning the
|
|
scheduler is running.
|
|
|
|
Examples in C++
|
|
---------------
|
|
.. code-block:: cpp
|
|
|
|
#include <chrono>
|
|
|
|
#include "pw_chrono/system_clock.h"
|
|
#include "pw_thread/sleep.h"
|
|
|
|
using std::literals::chrono_literals::ms;
|
|
|
|
void FunctionInvokedByThread() {
|
|
pw::this_thread::sleep_for(42ms);
|
|
}
|
|
|
|
void AnotherFunctionInvokedByThread() {
|
|
pw::this_thread::sleep_until(pw::chrono::SystemClock::now() + 42ms);
|
|
}
|
|
|
|
C
|
|
=
|
|
.. cpp:function:: void pw_this_thread_SleepFor(pw_chrono_SystemClock_Duration sleep_duration)
|
|
|
|
Invokes ``pw::this_thread::sleep_until(sleep_duration)``.
|
|
|
|
.. cpp:function:: void pw_this_thread_SleepUntil(pw_chrono_SystemClock_TimePoint wakeup_time)
|
|
|
|
Invokes ``pw::this_thread::sleep_until(wakeup_time)``.
|
|
|
|
|
|
---------------
|
|
Thread Yielding
|
|
---------------
|
|
C++
|
|
===
|
|
.. cpp:function:: void pw::this_thread::yield() noexcept
|
|
|
|
Provides a hint to the implementation to reschedule the execution of threads,
|
|
allowing other threads to run.
|
|
|
|
The exact behavior of this function depends on the implementation, in
|
|
particular on the mechanics of the OS scheduler in use and the state of the
|
|
system.
|
|
|
|
**Precondition:** This can only be called from a thread, meaning the
|
|
scheduler is running.
|
|
|
|
Example in C++
|
|
---------------
|
|
.. code-block:: cpp
|
|
|
|
#include "pw_thread/yield.h"
|
|
|
|
void FunctionInvokedByThread() {
|
|
pw::this_thread::yield();
|
|
}
|
|
|
|
C
|
|
=
|
|
.. cpp:function:: void pw_this_thread_Yield(void)
|
|
|
|
Invokes ``pw::this_thread::yield()``.
|
|
|
|
---------------------
|
|
Thread Identification
|
|
---------------------
|
|
The class ``pw::thread::Id`` is a lightweight, trivially copyable class that
|
|
serves as a unique identifier of Thread objects.
|
|
|
|
Instances of this class may also hold the special distinct value that does
|
|
not represent any thread. Once a thread has finished, the value of its
|
|
Thread::id may be reused by another thread.
|
|
|
|
This class is designed for use as key in associative containers, both ordered
|
|
and unordered.
|
|
|
|
Although the current API is similar to C++11 STL
|
|
`std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_, it is
|
|
missing the required hashing and streaming operators and may diverge further in
|
|
the future.
|
|
|
|
A thread's identification (``pw::thread::Id``) can be acquired only in C++ in
|
|
one of two ways:
|
|
|
|
1) Using the ``pw::thread::Thread`` handle's ``pw::thread::Id get_id() const``
|
|
method.
|
|
2) While executing the thread using
|
|
``pw::thread::Id pw::this_thread::get_id() noexcept``.
|
|
|
|
.. cpp:function:: pw::thread::Id pw::this_thread::get_id() noexcept
|
|
|
|
This is thread safe, not IRQ safe. It is implementation defined whether this
|
|
is safe before the scheduler has started.
|
|
|
|
|
|
Example
|
|
=======
|
|
.. code-block:: cpp
|
|
|
|
#include "pw_thread/id.h"
|
|
|
|
void FunctionInvokedByThread() {
|
|
const pw::thread::Id my_id = pw::this_thread::get_id();
|
|
}
|
|
|
|
---------------
|
|
Thread Creation
|
|
---------------
|
|
The class ``pw::thread::Thread`` can represent a single thread of execution.
|
|
Threads allow multiple functions to execute concurrently.
|
|
|
|
The Thread's API is C++11 STL
|
|
`std::thread <https://en.cppreference.com/w/cpp/thread/thread>`_ like, meaning
|
|
the object is effectively a thread handle and not an object which contains the
|
|
thread's context. Unlike ``std::thread``, the API requires
|
|
``pw::thread::Options`` as an argument and is limited to only work with
|
|
``pw::thread::ThreadCore`` objects and functions which match the
|
|
``pw::thread::Thread::ThreadRoutine`` signature.
|
|
|
|
We recognize that the C++11's STL ``std::thread``` API has some drawbacks where
|
|
it is easy to forget to join or detach the thread handle. Because of this, we
|
|
offer helper wrappers like the ``pw::thread::DetachedThread``. Soon we will
|
|
extend this by also adding a ``pw::thread::JoiningThread`` helper wrapper which
|
|
will also have a lighter weight C++20 ``std::jthread`` like cooperative
|
|
cancellation contract to make joining safer and easier.
|
|
|
|
Threads may begin execution immediately upon construction of the associated
|
|
thread object (pending any OS scheduling delays), starting at the top-level
|
|
function provided as a constructor argument. The return value of the
|
|
top-level function is ignored. The top-level function may communicate its
|
|
return value by modifying shared variables (which may require
|
|
synchronization, see :ref:`module-pw_sync`)
|
|
|
|
Thread objects may also be in the state that does not represent any thread
|
|
(after default construction, move from, detach, or join), and a thread of
|
|
execution may be not associated with any thread objects (after detach).
|
|
|
|
No two Thread objects may represent the same thread of execution; Thread is
|
|
not CopyConstructible or CopyAssignable, although it is MoveConstructible and
|
|
MoveAssignable.
|
|
|
|
.. list-table::
|
|
|
|
* - *Supported on*
|
|
- *Backend module*
|
|
* - FreeRTOS
|
|
- :ref:`module-pw_thread_freertos`
|
|
* - ThreadX
|
|
- :ref:`module-pw_thread_threadx`
|
|
* - embOS
|
|
- :ref:`module-pw_thread_embos`
|
|
* - STL
|
|
- :ref:`module-pw_thread_stl`
|
|
* - Zephyr
|
|
- Planned
|
|
* - CMSIS-RTOS API v2 & RTX5
|
|
- Planned
|
|
|
|
Module Configuration Options
|
|
============================
|
|
The following configurations can be adjusted via compile-time configuration of
|
|
this module, see the
|
|
:ref:`module documentation <module-structure-compile-time-configuration>` for
|
|
more details.
|
|
|
|
.. c:macro:: PW_THREAD_CONFIG_LOG_LEVEL
|
|
|
|
The log level to use for this module. Logs below this level are omitted.
|
|
|
|
Options
|
|
=======
|
|
The ``pw::thread::Options`` contains the parameters or attributes needed for a
|
|
thread to start.
|
|
|
|
Pigweed does not generalize options, instead we strive to give you full control
|
|
where we provide helpers to do this.
|
|
|
|
Options are backend specific and ergo the generic base class cannot be
|
|
directly instantiated.
|
|
|
|
The attributes which can be set through the options are backend specific
|
|
but may contain things like the thread name, priority, scheduling policy,
|
|
core/processor affinity, and/or an optional reference to a pre-allocated
|
|
Context (the collection of memory allocations needed for a thread to run).
|
|
|
|
Options shall NOT permit starting as detached, this must be done explicitly
|
|
through the Thread API.
|
|
|
|
Options must not contain any memory needed for a thread to run (TCB,
|
|
stack, etc.). The Options may be deleted or re-used immediately after
|
|
starting a thread.
|
|
|
|
Please see the thread creation backend documentation for how their Options work.
|
|
|
|
Portable Thread Creation
|
|
========================
|
|
Due to the fact that ``pw::thread::Options`` cannot be created in portable code,
|
|
some extra work must be done in order to permit portable thread creation.
|
|
Namely, a reference to the portable ``pw::thread::Options`` base class interface
|
|
must be provided through a header or extern which points to an instantiation in
|
|
non-portable code.
|
|
|
|
This can be most easily done through a facade and set of backends. This approach
|
|
can be powerful; enabling multithreaded unit/integration testing which can run
|
|
on both the host and on a device with the device's exact thread options.
|
|
|
|
Alternatively, it can also be be injected at build time by instantiating backend
|
|
specific build rule which share the same common portable source file(s) but
|
|
select backend specific source files and/or dependencies which provide the
|
|
non-portable option instantiations.
|
|
|
|
As an example, let's say we want to create a thread on the host and on a device
|
|
running FreeRTOS. They could use a facade which contains a ``threads.h`` header
|
|
with the following contents:
|
|
|
|
.. code-block:: cpp
|
|
|
|
// Contents of my_app/threads.h
|
|
#pragma once
|
|
|
|
#include "pw_thread/options.h"
|
|
|
|
namespace my_app {
|
|
|
|
const pw::thread::Options& HellowWorldThreadOptions();
|
|
|
|
} // namespace my_app
|
|
|
|
This could then be backed by two different backend implementations based on
|
|
the thread backend. For example for the STL the backend's ``stl_threads.cc``
|
|
source file may look something like:
|
|
|
|
.. code-block:: cpp
|
|
|
|
// Contents of my_app/stl_threads.cc
|
|
#include "my_app/threads.h"
|
|
#include "pw_thread_stl/options.h"
|
|
|
|
namespace my_app {
|
|
|
|
const pw::thread::Options& HelloWorldThreadOptions() {
|
|
static constexpr auto options = pw::thread::stl::Options();
|
|
return options;
|
|
}
|
|
|
|
} // namespace my_app
|
|
|
|
While for FreeRTOS the backend's ``freertos_threads.cc`` source file may look
|
|
something like:
|
|
|
|
.. code-block:: cpp
|
|
|
|
// Contents of my_app/freertos_threads.cc
|
|
#include "FreeRTOS.h"
|
|
#include "my_app/threads.h"
|
|
#include "pw_thread_freertos/context.h"
|
|
#include "pw_thread_freertos/options.h"
|
|
#include "task.h"
|
|
|
|
namespace my_app {
|
|
|
|
StaticContextWithStack<kHelloWorldStackWords> hello_world_thread_context;
|
|
const pw::thread::Options& HelloWorldThreadOptions() {
|
|
static constexpr auto options =
|
|
pw::thread::freertos::Options()
|
|
.set_name("HelloWorld")
|
|
.set_static_context(hello_world_thread_context)
|
|
.set_priority(kHelloWorldThreadPriority);
|
|
return options;
|
|
}
|
|
|
|
} // namespace my_app
|
|
|
|
|
|
Detaching & Joining
|
|
===================
|
|
The ``Thread::detach()`` API is always available, to let you separate the
|
|
thread of execution from the thread object, allowing execution to continue
|
|
independently.
|
|
|
|
The joining API, more specifically ``Thread::join()``, is conditionally
|
|
available depending on the selected backend for thread creation and how it is
|
|
configured. The backend is responsible for providing the
|
|
``PW_THREAD_JOINING_ENABLED`` macro through
|
|
``pw_thread_backend/thread_native.h``. This ensures that any users which include
|
|
``pw_thread/thread.h`` can use this macro if needed.
|
|
|
|
Please see the selected thread creation backend documentation for how to
|
|
enable joining if it's not already enabled by default.
|
|
|
|
.. Warning::
|
|
A constructed ``pw::thread::Thread`` which represents a thread of execution
|
|
must be EITHER detached or joined, else the destructor will assert!
|
|
|
|
DetachedThread
|
|
==============
|
|
To make it slightly easier and cleaner to spawn detached threads without having
|
|
to worry about thread handles, a wrapper ``DetachedThread()`` function is
|
|
provided which creates a ``Thread`` and immediately detaches it. For example
|
|
instead of:
|
|
|
|
.. code-block:: cpp
|
|
|
|
Thread(options, foo).detach();
|
|
|
|
You can instead use this helper wrapper to:
|
|
|
|
.. code-block:: cpp
|
|
|
|
DetachedThread(options, foo);
|
|
|
|
The arguments are directly forwarded to the Thread constructor and ergo exactly
|
|
match the Thread constuctor arguments for creating a thread of execution.
|
|
|
|
|
|
ThreadRoutine & ThreadCore
|
|
==========================
|
|
Threads must either be invoked through a
|
|
``pw::thread::Thread::ThreadRoutine``` style function or implement the
|
|
``pw::thread::ThreadCore`` interface.
|
|
|
|
.. code-block:: cpp
|
|
|
|
namespace pw::thread {
|
|
|
|
// This function may return.
|
|
using Thread::ThreadRoutine = void (*)(void* arg);
|
|
|
|
class ThreadCore {
|
|
public:
|
|
virtual ~ThreadCore() = default;
|
|
|
|
// The public API to start a ThreadCore, note that this may return.
|
|
void Start() { Run(); }
|
|
|
|
private:
|
|
// This function may return.
|
|
virtual void Run() = 0;
|
|
};
|
|
|
|
} // namespace pw::thread;
|
|
|
|
|
|
To use the ``pw::thread::Thread::ThreadRoutine``, your function must have the
|
|
following signature:
|
|
|
|
.. code-block:: cpp
|
|
|
|
void example_thread_entry_function(void *arg);
|
|
|
|
|
|
To invoke a member method of a class a static lambda closure can be used
|
|
to ensure the dispatching closure is not destructed before the thread is
|
|
done executing. For example:
|
|
|
|
.. code-block:: cpp
|
|
|
|
class Foo {
|
|
public:
|
|
void DoBar() {}
|
|
};
|
|
Foo foo;
|
|
|
|
static auto invoke_foo_do_bar = [](void *void_foo_ptr) {
|
|
// If needed, additional arguments could be set here.
|
|
static_cast<Foo*>(void_foo_ptr)->DoBar();
|
|
};
|
|
|
|
// Now use the lambda closure as the thread entry, passing the foo's
|
|
// this as the argument.
|
|
Thread thread(options, invoke_foo_do_bar, &foo);
|
|
thread.detach();
|
|
|
|
|
|
Alternatively, the aforementioned ``pw::thread::ThreadCore`` interface can be
|
|
be implemented by an object by overriding the private
|
|
``void ThreadCore::Run();`` method. This makes it easier to create a thread, as
|
|
a static lambda closure or function is not needed to dispatch to a member
|
|
function without arguments. For example:
|
|
|
|
.. code-block:: cpp
|
|
|
|
class Foo : public ThreadCore {
|
|
private:
|
|
void Run() override {}
|
|
};
|
|
Foo foo;
|
|
|
|
// Now create the thread, using foo directly.
|
|
Thread(options, foo).detach();
|
|
|
|
.. Warning::
|
|
Because the thread may start after the pw::Thread creation, an object which
|
|
implements the ThreadCore MUST meet or exceed the lifetime of its thread of
|
|
execution!
|
|
|
|
-----------------------
|
|
pw_snapshot integration
|
|
-----------------------
|
|
``pw_thread`` provides some light, optional integration with pw_snapshot through
|
|
helper functions for populating a ``pw::thread::Thread`` proto. Some of these
|
|
are directly integrated into the RTOS thread backends to simplify the thread
|
|
state capturing for snapshots.
|
|
|
|
SnapshotStack()
|
|
===============
|
|
The ``SnapshotStack()`` helper captures stack metadata (stack pointer and
|
|
bounds) into a ``pw::thread::Thread`` proto. After the stack bounds are
|
|
captured, execution is passed off to the thread stack collection callback to
|
|
capture a backtrace or stack dump. Note that this function does NOT capture the
|
|
thread name: that metadata is only required in cases where a stack overflow or
|
|
underflow is detected.
|
|
|
|
Python processor
|
|
================
|
|
Threads captured as a Thread proto message can be dumped or further analyzed
|
|
using using ``pw_thread``'s Python module. This is directly integrated into
|
|
pw_snapshot's processor tool to automatically provide rich thread state dumps.
|
|
|
|
The ``ThreadSnapshotAnalyzer`` class may also be used directly to identify the
|
|
currently running thread and produce symbolized thread dumps.
|
|
|
|
.. Warning::
|
|
Snapshot integration is a work-in-progress and may see significant API
|
|
changes.
|