The pw_thread module contains utilities for thread creation and thread execution.


This module is still under construction, the API is not yet stable.

Thread Sleeping#


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.

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++#

#include <chrono>

#include "pw_chrono/system_clock.h"
#include "pw_thread/sleep.h"

using std::literals::chrono_literals::ms;

void FunctionInvokedByThread() {

void AnotherFunctionInvokedByThread() {
  pw::this_thread::sleep_until(pw::chrono::SystemClock::now() + 42ms);


void pw_this_thread_SleepFor(pw_chrono_SystemClock_Duration sleep_duration)#

Invokes pw::this_thread::sleep_until(sleep_duration).

void pw_this_thread_SleepUntil(pw_chrono_SystemClock_TimePoint wakeup_time)#

Invokes pw::this_thread::sleep_until(wakeup_time).

Thread Yielding#


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++#

#include "pw_thread/yield.h"

void FunctionInvokedByThread() {


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, 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.

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.


#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 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 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.

Supported on

Backend module













Module Configuration Options#

The following configurations can be adjusted via compile-time configuration of this module, see the module documentation for more details.


The log level to use for this module. Logs below this level are omitted.


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.

Options subclass must contain non-default explicit constructor (parametrized or not), e.g. constexpr Options() {}. It is not enough to have them as = default ones, because C++17 considers subclasses like stl::Options as aggregate classes if they have a default constructor and requires base class constructor to be public (which is not the case for the thread::Options) for Options{} syntax.

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:

// 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 source file may look something like:

// Contents of my_app/
#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 source file may look something like:

// Contents of my_app/
#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 =
  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.


A constructed pw::thread::Thread which represents a thread of execution must be EITHER detached or joined, else the destructor will assert!


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:

Thread(options, foo).detach();

You can instead use this helper wrapper to:

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.

namespace pw::thread {

// This function may return.
using Thread::ThreadRoutine = void (*)(void* arg);

class ThreadCore {
  virtual ~ThreadCore() = default;

  // The public API to start a ThreadCore, note that this may return.
  void Start() { Run(); }

  // 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:

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:

class Foo {
  void DoBar() {}
Foo foo;

static auto invoke_foo_do_bar = [](void *void_foo_ptr) {
    //  If needed, additional arguments could be set here.

// 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);

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:

class Foo : public ThreadCore {
  void Run() override {}
Foo foo;

// Now create the thread, using foo directly.
Thread(options, foo).detach();


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!

Thread Iteration#


Status ForEachThread(const ThreadCallback &cb)#

Calls the provided callback for each thread that has not been joined/deleted.

This function provides a generalized subset of information that a TCB might contain to make it easier to introspect system state. Depending on the RTOS and its configuration, some of these fields may not be populated, so it is important to check that they have values before attempting to access them.

Warning: The function may disable the scheduler to perform a runtime capture of thread information.

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.


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.


Snapshot integration is a work-in-progress and may see significant API changes.