.. _seed-0128: ================================= 0128: Abstracting Thread Creation ================================= .. seed:: :number: 128 :name: Abstracting Thread Creation :status: Accepted :proposal_date: 2024-04-25 :cl: 206670 :authors: Wyatt Hepler :facilitator: Taylor Cramer ------- Summary ------- This SEED proposes supporting cross-platform thread creation with ``pw_thread``. It introduces APIs for creating a thread without referring to the specific OS / ``pw_thread`` backend. This dramatically simplifies thread creation for the :ref:`vast majority ` of production use cases. It does so without sacrificing configurability or limiting users in any way. Key new features ================ - ``pw::ThreadAttrs`` describes cross-platform thread attributes: - Thread name. - Stack size. - ``pw::ThreadPriority`` to represent a thread's priority. - ``pw::ThreadContext`` represents the resources required to run one thread. - ``pw::Thread`` can be started from ``ThreadAttrs`` and ``ThreadContext``. - Additions to the ``pw_thread`` facade to support the new functionality. pw_thread API overview ====================== With these changes, the key pw_thread features are as follows: .. topic:: Thread creation API Key Types - ``pw::Thread`` -- Thread handle. The thread might be unstarted, running, or completed. - ``pw::thread::Options`` -- Base class for platform-specific thread options. priority. - ``pw::ThreadAttrs`` -- Generic thread attributes: name, size, priority. may include stack. - ``pw::ThreadPriority`` -- Generic thread priority, with relative modifiers. - ``pw::ThreadContext`` -- Generic thread resources. Depending on backend, - ``pw::ThreadStack`` -- Optionally specify a thread stack separately from the context. Key methods - ``pw::Thread`` -- Constructor, ``join()``. - ``pw::ThreadAttrs`` -- ``set_name(name)``, ``set_priority(priority)``, ``set_stack_size_bytes(bytes)``. - ``pw::ThreadPriority`` -- ``Low()``, ``Medium()``, ``High()``, ``NextHigher()``, ``NextLower()``, etc. Example ======= .. code-block:: c++ // "example_project/threads.h" // Define thread attributes for the main thread. constexpr pw::ThreadAttrs kMainThread = pw::ThreadAttrs() .set_name("app") .set_priority(pw::ThreadPriority::Medium()), .set_stack_size_bytes(MY_PROJECT_MAIN_STACK_SIZE_BYTES); // Define attributes for another thread, based on kMainThread. constexpr pw::ThreadAttrs kLogThread = pw::ThreadAttrs(kMainThread) .set_name("logging") .set_priority_next_lower(); .. code-block:: c++ // "example_project/main.cc" #include "example_project/threads.h" // Declare a thread context that can be used to start a thread. pw::ThreadContext app_thread_context; // Declare thread contexts associated with specific ThreadAttrs. pw::ThreadContext main_thread_context; pw::ThreadContext log_thread_context; // Thread handle for a non-detached thread. pw::Thread app_thread; void StartThreads() { // Start the main and logging threads. pw::Thread(main_thread_context, MainThreadBody).detach(); pw::Thread(log_thread_context, LoggingThreadBody).detach(); // Start an app thread that uses the app_thread_context. Since the stack size // is not specified, the full stack provided by app_thread_context is used. app_thread = pw::Thread( app_thread_context, pw::ThreadAttrs().set_name("app 1"), AppThreadBody1); } void MainThreadBody() { // Join the "app 1" thread and reuse the app_thread_context for a new thread. app_thread.join(); app_thread = pw::Thread( app_thread_context, pw::ThreadAttrs().set_name("app 2"), AppThreadBody2); ... } ---------- Motivation ---------- Pigweed's ``pw_thread`` module does not support cross-platform thread creation. Instead, threads must be created by instantiating a :cpp:class:`pw::thread::Options` specific to the thread backend. For example, to create a FreeRTOS thread, one must instantiate a :cpp:class:`pw::thread::freertos::Options` and configure it with a :cpp:class:`pw::thread::freertos::Context` Cross-platform thread creation was intentionally avoided in the ``pw_thread`` API. It is not possible to specify thread attributes in a truly generic, portable way. Every OS/RTOS exposes a different set of thread parameters, and settings for one platform may behave completely differently or not exist on another. Cross-platform thread creation may not be possible to do perfectly, but avoiding it has significant downsides. - The current APIs optimize for control at the expense of usability. Thread creation is complex. - Developers always have to deal with the full complexity of thread creation, even for simple cases or when just getting started. - Users must learn a slightly different API for each RTOS. The full ``Thread`` API cannot be documented in one place. - Cross-platform code that creates threads must call functions that return ``pw::thread::Options``. Each platform implements the functions as needed. This requires exposing threads in the public API. Libraries such as :ref:`module-pw_system` cannot add internal threads without breaking their users. - Code for creating ``pw::thread::Options`` must be duplicated for each platform. - Projects avoid writing cross-platform code and tests due to the complexity of thread creation. ``pw_system`` and threads ========================= Currently, running :ref:`module-pw_system` requires writing custom low-level code that is aware of both ``pw_system`` and the RTOS it is running on (see e.g. `boot.cc `_ and `target_hooks.cc `_). Enabling cross-platform thread creation would make it easier to use ``pw_system``. The code for running ``pw_system`` on any target would be the same: a single function call in ``main``. The user would no longer have to allocate stacks or create :cpp:class:`pw::thread::Options` for ``pw_system`` threads; this could be managed by ``pw_system`` itself and configured with generic ``pw_system`` options if needed. Cross-platform thread creation also makes it easier for ``pw_system`` users to write their own code. Setting up a thread takes just two lines of code and no interactions with RTOS-specific APIs. A ``pw_system`` application created this way can run on any platform out of the box. --------------------- Problem investigation --------------------- Various cross-platform threading APIs exist today. C++ Standard Library ==================== The C++ Standard Library currently provides a limited cross-platform thread creation API in ````. No thread attributes are exposed; threads are created with platform defaults. An effort is underway to standardize some thread attributes, giving users more control over threads while maintaining portability. See `P2019 -- Thread attributes `_ for details. The latest proposal exposes the thread name and stack size. Some alternatives have also been proposed (`P3072 `_). POSIX ===== POSIX is a portable operating system API. The POSIX thread creation function ``pthread_create`` takes a pointer to a ``pthread_attr_t`` struct. This struct may a support a wide variety thread options that are configured with functions such as ``pthread_attr_setstacksize``, ``pthread_attr_setschedpolicy``, and others. A thread's name can be set with ``pthread_setname_np``. See `man pthreads `_ for details. CMSIS-RTOS ========== The `CMSIS-RTOS2 API `_ provides a generic RTOS interface intended for use with Arm Cortex devices. CMSIS-RTOS2 is implemented by several operating systems, including FreeRTOS and Arm's own Keil RTX5. CMSIS-RTOS2 provides a comprehensive set of thread attributes in its `osThreadAttr_t `_ struct. It also provides functions for initializing and controlling the scheduler, such as `osKernelStart `_. -------- Proposal -------- The new cross-platform API does not replace the existing backend-specific thread creation APIs. The new API supports most production use cases, but does not expose the full capabilities and configuration of all supported RTOSes. It is intended to be easy to adopt, while providing a frictionless pathway to the current, fully configurable APIs if needed. With this proposal, per-target thread creation is simply a matter of setting variables differently for each target. This removes the need for duplicated code for creating platform-specific thread contexts and ``pw::thread::Options``. Generic thread attributes ========================= This SEED introduces a limited set of cross-platform thread attributes. These generic attributes map to a platform-specific :cpp:class:`pw::thread::Options`. There are three thread attributes: - Name - Stack size - Priority Other attributes may be added in the future, such as dynamic or static resource allocation. Thread attributes are provided only as hints to the backend. Backends should respect thread attributes, if possible, but may ignore or adapt them depending on the OS's capabilities. Backends cannot fail to create thread because of how thread attributes are set, but users may check the backend's capabilities, such as whether thread priorities are supported, as needed. Examples of acceptable adaptations to thread attributes. - Ignore the thread name and stack size because the underlying API does not support specifying them (e.g. C++'s ````). - Silently truncate a thread name because the underlying RTOS only supports shorter names. - Round up to the minimum required stack size from a smaller requested stack size. - Add a fixed amount to a requested stack size to account for RTOS overhead. - Dynamically allocate the thread stack if it is above a certain size; statically allocate it otherwise. .. _seed-0128-thread-config-survey: Why these thread attributes? ---------------------------- A survey of thread creation with Pigweed across a few large, production projects found that 99% of their thread configurations can be exactly represented with thread name, priority, stack size. The only exception was a single RTOS feature used in a few threads in one project. The proof is in the pudding: ``pw_thread`` users almost never need low-level, RTOS-specific threading features. Abstracting these three thread attributes dramatically simplifies thread creation, resulting in more portable, easier-to-test code. In the rare cases when more control is needed, the existing non-portable ``pw_thread`` API is ready to use. OS / RTOS support for thread attributes --------------------------------------- Most OS APIs support the proposed thread attributes. .. list-table:: :header-rows: 1 * - OS / API - function - name - stack size - priority type - priority levels * - C++ ```` - `std::thread `_ - none - none - none - none * - POSIX - `pthread_create `_ - `C string `_ - `bytes `_ - `pthread_attr_setschedparam `_ - `at least 32 `_ * - `CMSIS-RTOS2 / Keil RTX5 `_ - `osThreadNew `_ - `C string `__ - bytes - `osPriority_t `_ - 56 * - `embOS `_ - ``OS_TASK_Create()`` - | C string | uses pointer - bytes - ``unsigned int`` - 2³²-2 * - `FreeRTOS `_ - `xTaskCreateStatic `_ - | C string | copies `configMAX_TASK_NAME_LEN `_ - words - `unsigned int `_ - | `configMAX_PRIORITIES `_ | `≤32 in some configs `_ * - `NuttX `_ - | `task_create `_ | (also POSIX APIs) - C string - bytes - ``int`` - `256 `_ * - `ThreadX `_ - `tx_thread_create `_ - `C string `__ - bytes - ``unsigned int`` (``TX_MAX_PRIORITIES - 1``)–0 (0 highest) - `multiple of 32 `_ * - ``pw::ThreadContext`` - :cpp:type:`pw::Thread` - C string - bytes - custom class - same as underying OS Creating threads ================ The APIs proposed in this SEED streamline thread creation for common use cases, while allowing for full configuration when necessary. Generally, projects should start with the minimum complexity required and increase the complexity only if more control is needed. Threads defined in upstream Pigweed should start with some configurability to avoid friction in downstream projects. Dynamic threads: "just give me a thread" ---------------------------------------- For simple cases, Pigweed will offer a new static ``pw::Thread::Start`` function. .. code-block:: c++ #include "pw_thread/thread.h" void CreateThreads() { pw::Thread::Start([] { /* thread body */ ).detach(); } .. admonition:: When should I use ``pw::Thread::Start``? - Experimenting - Prototyping Declare a default thread ------------------------ Create a thread with ``DefaultThreadContext`` and default attributes. The ``pw_thread`` backend starts a thread with a default name, stack size, and priority. .. code-block:: c++ #include "pw_thread/thread.h" pw::DefaultThreadContext context; void CreateThreads() { pw::Thread(context, pw::ThreadAttrs(), [] { /* thread body */ }).detach(); } .. admonition:: When should I use default thread contexts? - Experimenting - Prototyping - Testing - Getting started Configurable thread attributes ------------------------------ Define a ``pw::ThreadAttrs`` and use it to create threads with ``pw::ThreadContext<>``. Attributes are configured as needed using the project's configuration pattern. .. code-block:: c++ #include "pw_thread/thread.h" #include "project/config.h" constexpr auto kMyThread = pw::ThreadAttrs() .set_name("my thread") .set_priority(MY_THREAD_PRIORITY) .set_stack_size_bytes(kMyThreadStackSizeBytes); pw::ThreadContext my_thread_context; pw::Thread other_thread; pw::ThreadContext other_thread_context; void StartThreads() { pw::Thread(my_thread_context, [] { /* thread body */ }).detach(); other_thread = pw::Thread(other_thread_context, pw::ThreadAttrs().set_name("other"), OtherThreadBody); } Example configuration header: .. code-block:: c++ // "project/config.h" // Configurable thread priority. Can be changed by defining // MY_THREAD_PRIORITY in the build system. #ifndef MY_THREAD_PRIORITY #define MY_THREAD_PRIORITY pw::ThreadPriority::High() #endif // MY_THREAD_PRIORITY // Configuration may be based on the target platform. #if BUILDING_FOR_PLATFORM_A inline constexpr size_t kMyThreadStackSizeBytes = 2048; inline constexpr size_t kOtherThreadStackSizeBytes = 1024; #else inline constexpr size_t kMyThreadStackSizeBytes = 1536; inline constexpr size_t kOtherThreadStackSizeBytes = 512; #endif // BUILDING_FOR_PLATFORM_A .. admonition:: When should I use configurable thread attributes? - Pigweed upstream development - Production project development Platform-specific thread creation --------------------------------- In the rare case that platform-specific thread configuration is required, provide a function that returns ``NativeOptions`` or ``const Options&`` and use it to create a thread. The function may be a facade, so each target can implement it differently. Projects may provide a default implementation of the function that uses ``pw::ThreadAttrs``. This approach is equivalent to the original non-portable ``pw_thread`` creation pattern, optionally with a ``pw::ThreadAttrs``-based default implementation of the function. This approach is only necessary for threads that specifically require non-portable features. Other threads should continue to use ``pw::ThreadAttrs``. .. code-block:: c++ #include "pw_thread/thread.h" #include "project/config.h" // This function returns a `pw::thread::Options` for creating a thread. pw::thread::NativeOptions GetThreadOptions(); // Optionally, provide a default implementation of `GetThreadOptions()` that // uses `pw::ThreadAttrs`. #if !PROJECT_CFG_THREAD_CUSTOM_OPTIONS pw::thread::NativeOptions GetThreadOptions() { static constinit pw::ThreadContext context; return pw::thread::GetNativeOptions( context, pw::ThreadAttrs().set_name("thread name")); } #endif // !PROJECT_CFG_THREAD_CUSTOM_OPTIONS // Call `GetThreadOptions()` to create a thread. void CreateThreads() { pw::Thread(GetThreadOptions(), [] { /* thread body */ }).detach(); } Example configuration header: .. code-block:: c++ // project/config.h // Set to 1 to implement `GetThreadOptions()` and provide fully custom // `pw::thread::Options` for the platform. #ifndef PROJECT_CFG_THREAD_CUSTOM_OPTIONS #define PROJECT_CFG_THREAD_CUSTOM_OPTIONS 0 #endif // PROJECT_CFG_THREAD_CUSTOM_OPTIONS // Stack size setting for the default thread options. #ifndef PROJECT_CFG_THREAD_STACKS_SIZE_HINT #define PROJECT_CFG_THREAD_STACKS_SIZE_HINT 2048 #endif // PROJECT_CFG_THREAD_STACKS_SIZE_HINT namespace project::cfg { inline constexpr size_t kThreadStackSizeHintBytes = PROJECT_CFG_THREAD_STACKS_SIZE_HINT; } // namespace project::cfg This approach is not recommended as a starting point. It adds complexity that is unlikely to be necessary. Most projects should start with configurable ``ThreadAttrs`` and add switch to platform-specific thread configuration only for threads that need it. .. admonition:: When should I use platform-specific thread creation? - Pigweed upstream development, if a downstream user specifically requires platform-specific thread features for a thread defined by Pigweed. - Production project development that requires platform-specific thread features. C++ implementation details ========================== Facade additions ----------------- This proposal adds a few items to the ``pw_thread`` facade: - Aliases for the native context types wrapped by ``pw::ThreadContext``. - Information about the range of supported thread priorities used by ``pw::ThreadPriority``. - Alias for the native ``pw::thread::Options`` type. - Function that maps ``pw::ThreadContext`` and ``pw::ThreadAttrs`` to native ``pw::thread::Options``. These features are used by ``pw_thread`` classes, not end users. .. code-block:: c++ // pw_thread_backend/thread_native.h namespace pw::thread::backend { // Native, non-templated context (resources). using NativeContext = /* implementation-defined */; // Thread context with a stack size hint. Must derive from or be the same // type as `NativeContext`. Must be default constructible. template using NativeContextWithStack = /* implementation-defined */; // Stack size to use when unspecified; 0 for platforms that do not support // defining the stack size. inline constexpr size_t kDefaultStackSizeBytes = /* implementation-defined */; // Define the range of thread priority values. These values may represent a // subset of priorities supported by the OS. The `kHighestPriority` may be // numerically higher or lower than `kLowestPriority`, depending on the OS. // Backends that do not support priorities must set `kLowestPriority` and // `kHighestPriority` to the same value, and should use `int` for // `NativePriority`. using NativePriority = /* implementation-defined */; inline constexpr NativePriority kLowestPriority = /* implementation-defined */; inline constexpr NativePriority kHighestPriority = /* implementation-defined */; // Native options class derived from pw::thread::Options. using NativeOptions = /* implementation-defined */; // Converts cross-platform ThreadAttrs to NativeOptions. May be defined // in ``pw_thread_backend/thread_inline.h`` or in a .cc file. NativeOptions GetNativeOptions(NativeContext& context, const ThreadAttrs& attributes); } // namespace pw::thread::backend ``pw_thread_stl`` example implementation: .. code-block:: c++ namespace pw::thread::backend { using NativeContext = pw::thread::stl::Context; // Ignore the stack size since it's not supported. template using NativeContextWithStack = pw::thread::stl::Context; inline constexpr size_t kDefaultStackSizeBytes = 0; using NativePriority = int; inline constexpr NativePriority kLowestPriority = 0; inline constexpr NativePriority kHighestPriority = 0; using NativeOptions = pw::thread::stl::Options; inline NativeOptions GetNativeOptions(NativeContext&, const ThreadAttrs&) { return pw::thread::stl::Options(); } } // namespace pw::thread::backend ``pw_thread_freertos`` example implementation: .. code-block:: c++ namespace pw::thread::backend { using NativeContext = pw::thread::freertos::StaticContext; // Convert bytes to words, rounding up. template using NativeContextWithStack = pw::thread::stl::StaticContextWithStack< (kStackSizeBytes + sizeof(StackType_t) - 1) / sizeof(StackType_t)>; inline constexpr size_t kDefaultStackSizeBytes = pw::thread::freertos::config::kDefaultStackSizeWords; using NativePriority = UBaseType_t; inline constexpr NativePriority kLowestPriority = tskIDLE_PRIORITY; inline constexpr NativePriority kHighestPriority = configMAX_PRIORITIES - 1; using NativeOptions = pw::thread::freertos::Options; inline NativeOptions GetNativeOptions(NativeContext& context, const ThreadAttrs& attrs) { return pw::thread::freertos::Options() .set_static_context(context), .set_name(attrs.name()) .set_priority(attrs.priority().native()) } } // namespace pw::thread::backend ``ThreadPriority`` ------------------ Different OS APIs define priorities very differently. Some support a few priority levels, others support the full range of a ``uint32_t``. For some, 0 is the lowest priority and for others it is the highest. And changing the OS's scheduling policy might changes how threads are scheduled without changing their priorities. ``pw::ThreadPriority`` represents thread priority precisely but abstractly. It supports the following: - Represent the full range of priorities supported by the underlying OS. - Set priorities in absolute terms that map to OS priority ranges in a reasonable way. - Set priorities relative to one another. - Check that priorities are actually higher or lower than one another on a given platform at compile time. - Check if the backend supports thread priorities at all. Many projects will be able to define a single priority set for all platforms. The priorities may translate differently to each platforms, but this may not matter. If a single set of priorities does not work for all platforms, priorities can be configured per platform, like other attributes. Here is a high-level overview of the class: .. code-block:: c++ namespace pw { class ThreadPriority { public: // True if the backend supports different priority levels. static constexpr bool IsSupported(); // Named priorities. These priority levels span the backend's supported // priority range. // // The optional `kPlus` template parameter returns a priority the specified // number of levels higher than the named priority, but never exceeding the // priority of the next named level, if supported by the backend. static constexpr ThreadPriority VeryLow(); static constexpr ThreadPriority Low(); static constexpr ThreadPriority MediumLow(); static constexpr ThreadPriority Medium(); static constexpr ThreadPriority MediumHigh(); static constexpr ThreadPriority High(); static constexpr ThreadPriority VeryHigh(); // Refers to the lowest or highest priority supported by the OS. static constexpr ThreadPriority Lowest(); static constexpr ThreadPriority Highest(); // Returns the ThreadPriority with next distinct higher or lower value. If // the priority is already the highest/lowest, returns the same value. constexpr ThreadPriority NextLower(); constexpr ThreadPriority NextHigher(); // Returns the ThreadPriority with next distinct higher or lower value. // Asserts that the priority is not already the highest/lowest. constexpr ThreadPriority NextLowerChecked(); constexpr ThreadPriority NextHigherChecked(); // ThreadPriority supports comparison. This makes it possible, for example, // to static_assert that one priority is higher than another in the // backend. constexpr bool operator==(const ThreadPriority&); ... // Access the native thread priority type. These functions may be helpful // when ThreadPriority is configured separately for each platform. using native_type = backend::NativeThreadPriority; static constexpr FromNative(native_type native_priority); native_type native() const; }; } // namespace pw Example uses: .. code-block:: c++ // Named priorities are spread over the backend's supported priority range. constexpr pw::ThreadPriority kThreadOne = ThreadPriority::Low(); constexpr pw::ThreadPriority kThreadTwo = ThreadPriority::Medium(); // Define a priority one higher than Medium, but never equal to or greater // than the next named priority, MediumHigh, if possible in the given // backend. constexpr pw::ThreadPriority kThreadThree = ThreadPriority::Medium<1>(); // Set the priority exactly one backend priority level higher than // kThreadThree, if supported by the backend. constexpr pw::ThreadPriority kThreadFour = kThreadThree.NextHigher(); static_assert(!ThreadPriority::IsSupported() || kThreadThree < kThreadFour); .. tip:: It is recommended that projects pick a starting priority level (e.g. ``ThreadPriority::Lowest().NextHigher()``) and define all priorities relative to it. Mapping OS priorities to named priorities ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If thread priorities are not supported, all named priorities are the same level. If fewer than 7 levels are supported by the backend, some named levels map to the same OS priority. For example, if there are only 3 priority levels supported, then ``VeryLow == Low``, ``MediumLow == Medium == MediumHigh``, and ``High == VeryHigh``. For backends that support 7 or more priority levels, each named priority level is guaranteed to map to a unique OS priority. ``ThreadAttrs`` --------------- The ``ThreadAttrs`` class represents generic thread attributes. It is a cross-platform version of :cpp:class:`pw::thread::Options`. .. code-block:: c++ namespace pw { // Generic thread attributes. class ThreadAttrs { public: // Initializes ThreadAttrs to their backend-defined defaults. constexpr ThreadAttrs(); // ThreadAttrs can be copied to share properties between threads. constexpr ThreadAttrs(const ThreadAttrs&) = default; constexpr ThreadAttrs& operator=(const ThreadAttrs&) = default; // Name hint as a null-terminated string; never null. constexpr const char* name() const; constexpr ThreadAttrs& set_name(const char* name); constexpr Priority priority() const; constexpr ThreadAttrs& set_priority(Priority priority); // Increment or decrement the priority to set task priorities relative to // one another. constexpr ThreadAttrs& set_priority_next_higher(); constexpr ThreadAttrs& set_priority_next_lower(); constexpr size_t stack_size_bytes() const; constexpr ThreadAttrs& set_stack_size_bytes(size_t stack_size_bytes); }; } // namespace pw ``ThreadAttrs`` may be defined at runtime or as ``constexpr`` constants. Projects may find it helpful to define ``ThreadAttrs`` in a centralized location. .. code-block:: c++ #include "pw_thread/attrs.h" #include "my_project/config.h" namespace my_project { // Global list of thread attributes. inline constexpr auto kThreadOne = pw::ThreadAttrs() .set_name("thread one") .set_stack_size_bytes(1024) .set_priority(pw::ThreadPriority::Medium()); inline constexpr auto kThreadTwo = pw::ThreadAttrs(kThreadOne) .set_name("thread two"); inline constexpr auto kImportantThread = pw::ThreadAttrs() .set_name("important!") .set_stack_size_bytes(IMPORTANT_THREAD_STACK_SIZE_BYTES) .set_priority(IMPORTANT_THREAD_PRIORITY); inline constexpr auto kLessImportantThread = pw::ThreadAttrs() .set_name("also important!") .set_stack_size_bytes(IMPORTANT_THREAD_STACK_SIZE_BYTES) .set_priority(kImportantThread.priority().NextLower()); static_assert( !pw::ThreadPriority::IsSupported() || kImportantThread.priority() > kLessImportantThread.priority(), "If the platform supports priorities, ImportantThread must be higher " "priority than LessImportantThread"); } // namespace my_project ``ThreadContext`` ----------------- ``pw::ThreadContext`` represents the resources required to run one thread. This may include platform-specific handles, a statically allocated thread control block (TCB), or the thread's stack. If platforms do not require manual allocation for threads, ``pw::ThreadContext`` may be empty. ``ThreadContext`` is a generic wrapper around a backend-defined object. It prevents unintentional access of backend-specific features on the native object. ``ThreadContext`` objects may be reused if their associated thread has been joined. ``ThreadContext`` takes a few forms: - ``ThreadContext`` -- Context with internally allocated thread stack. - ``ThreadContext`` -- Context associated with a set of ``ThreadAttrs``. Uses internally or externally allocated stack based on the ``ThreadAttrs``. - ``ThreadContext<>`` -- Context with a runtime-provided ``ThreadStack``. .. code-block:: c++ namespace pw { // Represents the resources required for one thread. May include OS data // structures, the thread stack, or be empty, depending on the platform. // // ThreadContext may be reused or deleted if the associated thread is // joined. template class ThreadContext; // ThreadContext with integrated stack. template class ThreadContext { public: constexpr ThreadContext() = default; private: backend::NativeContextWithStack native_context_; }; // Alias for ThreadContext with the backend's default stack size. using DefaultThreadContext = ThreadContext; // Declares a ThreadContext that is associated with a specific set of thread // attributes. Internally allocates the stack if the stack size hint is set. // The ThreadContext may be reused if the associated thread is joined, but // all threads use the same ThreadAttrs. template class ThreadContext { private: ThreadContext context_; }; } // namespace pw #include "pw_thread_backend/thread_inline.h" ``ThreadStack`` --------------- Represents a thread stack of the specified size. The object may be empty if the backends dynamically allocate stacks. .. code-block:: c++ namespace pw { template class ThreadStack { private: backend::NativeThreadStack native_stack_; }; } // namespace pw ``ThreadStack`` may specified separately from the ``ThreadContext`` if users have need to declare stacks in different sections or want to keep them separate from other items in the ``ThreadContext``. The ``ThreadStack`` is set on the ``ThreadAttrs`` instead of the stack size: .. code-block:: c++ STACK_SECTION alignas(256) constinit ThreadStack kMainStack; constexpr pw::ThreadAttrs kMainThread = pw::ThreadAttrs() .set_name("MainThread") .set_stack(kMainStack) .set_priority(kMainPriority); ThreadContext kMainThreadContext; void RunThread() { pw::Thread(kMainThreadContext, [] { /* thread body */ }).detach(); } ``ThreadContext`` objects that are not associated with a ``ThreadAttrs`` work similarly: .. code-block:: c++ STACK_SECTION alignas(256) constinit ThreadStack kAppStack; ThreadContext<> kAppThreadContext; void RunThreads() { pw::Thread thread(kAppThreadContext, pw::ThreadAttrs().set_stack(kAppStack).set_name("T1"), [] { /* thread body */ }); thread.join() pw::Thread thread(kAppThreadContext, pw::ThreadAttrs().set_stack(kAppStack).set_name("T2"), [] { /* thread body */ }); thread.join(); } The ``STACK_SECTION`` macro would be provided by a config header: .. code-block:: c++ #if BUILDING_FOR_DEVICE_A #define STACK_SECTION PW_PLACE_IN_SECTION(".thread_stacks") #else // building for device B #define STACK_SECTION // section doesn't matter #endif // BUILDING_FOR_DEVICE_A ``Thread`` additions -------------------- ``pw::Thread`` will accept ``ThreadContext`` and ``ThreadAttrs``. .. code-block:: c++ class Thread { // Existing constructor. Thread(const Options& options, Function&& entry) // Creates a thread with a ThreadContext associated with a ThreadAttrs. template Thread(ThreadContext& context, Function&& entry); // Creates a thread from attributes passed in a template parameter. template Thread(ThreadContext& context, Function&& entry); // Creates a thread from context and attributes. Performs a runtime check // that the ThreadContext's stack is large enough, which can be avoided by // using one of the other constructors. template Thread(ThreadContext& context, const ThreadAttrs& attributes, Function&& entry); // Creates a thread with the provided context and attributes. The // attributes have a ThreadStack set. Thread(ThreadContext<>& context, const ThreadAttrs& attributes, Function&& entry); Dynamic thread creation function -------------------------------- The ``pw::Thread::Start`` function starts a thread as simply as possible. It starts returns a ``pw::Thread`` that runs a user-provided function. Users may optionally provide ``pw::ThreadAttrs``. ``pw::Thread::Start`` is implemented with a new, separate facade. The backend may statically or dynamically allocate resources. A default backend that statically allocates resources for a fixed number of threads will be provided in upstream Pigweed. .. code-block:: c++ namespace pw { class Thread { ... // Starts running the thread_body in a separate thread. The thread is // allocated and managed by the backend. template static Thread Start(Function&& thread_body, Args&&... args); template static Thread Start(const pw::ThreadAttrs& attributes, Function&& thread_body, Args&&... args); }; } // namespace pw