1.. _seed-0128: 2 3================================= 40128: Abstracting Thread Creation 5================================= 6.. seed:: 7 :number: 128 8 :name: Abstracting Thread Creation 9 :status: Accepted 10 :proposal_date: 2024-04-25 11 :cl: 206670 12 :authors: Wyatt Hepler 13 :facilitator: Taylor Cramer 14 15------- 16Summary 17------- 18This SEED proposes supporting cross-platform thread creation with ``pw_thread``. 19It introduces APIs for creating a thread without referring to the specific OS / 20``pw_thread`` backend. This dramatically simplifies thread creation for the 21:ref:`vast majority <seed-0128-thread-config-survey>` of production use cases. 22It does so without sacrificing configurability or limiting users in any way. 23 24Key new features 25================ 26- ``pw::ThreadAttrs`` describes cross-platform thread attributes: 27 28 - Thread name. 29 - Stack size. 30 - ``pw::ThreadPriority`` to represent a thread's priority. 31 32- ``pw::ThreadContext`` represents the resources required to run one thread. 33- ``pw::Thread`` can be started from ``ThreadAttrs`` and ``ThreadContext``. 34- Additions to the ``pw_thread`` facade to support the new functionality. 35 36pw_thread API overview 37====================== 38With these changes, the key pw_thread features are as follows: 39 40.. topic:: Thread creation API 41 42 Key Types 43 44 - ``pw::Thread`` -- Thread handle. The thread might be unstarted, running, or 45 completed. 46 - ``pw::thread::Options`` -- Base class for platform-specific thread options. 47 priority. 48 - ``pw::ThreadAttrs`` -- Generic thread attributes: name, size, priority. 49 may include stack. 50 - ``pw::ThreadPriority`` -- Generic thread priority, with relative modifiers. 51 - ``pw::ThreadContext`` -- Generic thread resources. Depending on backend, 52 - ``pw::ThreadStack`` -- Optionally specify a thread stack separately from 53 the context. 54 55 Key methods 56 57 - ``pw::Thread`` -- Constructor, ``join()``. 58 - ``pw::ThreadAttrs`` -- ``set_name(name)``, ``set_priority(priority)``, 59 ``set_stack_size_bytes(bytes)``. 60 - ``pw::ThreadPriority`` -- ``Low()``, ``Medium()``, ``High()``, 61 ``NextHigher()``, ``NextLower()``, etc. 62 63Example 64======= 65.. code-block:: c++ 66 67 // "example_project/threads.h" 68 69 // Define thread attributes for the main thread. 70 constexpr pw::ThreadAttrs kMainThread = pw::ThreadAttrs() 71 .set_name("app") 72 .set_priority(pw::ThreadPriority::Medium()), 73 .set_stack_size_bytes(MY_PROJECT_MAIN_STACK_SIZE_BYTES); 74 75 // Define attributes for another thread, based on kMainThread. 76 constexpr pw::ThreadAttrs kLogThread = pw::ThreadAttrs(kMainThread) 77 .set_name("logging") 78 .set_priority_next_lower(); 79 80.. code-block:: c++ 81 82 // "example_project/main.cc" 83 84 #include "example_project/threads.h" 85 86 // Declare a thread context that can be used to start a thread. 87 pw::ThreadContext<MY_PROJECT_APP_STACK_SIZE_BYTES> app_thread_context; 88 89 // Declare thread contexts associated with specific ThreadAttrs. 90 pw::ThreadContext<kMainThread> main_thread_context; 91 pw::ThreadContext<kLogThread> log_thread_context; 92 93 // Thread handle for a non-detached thread. 94 pw::Thread app_thread; 95 96 void StartThreads() { 97 // Start the main and logging threads. 98 pw::Thread(main_thread_context, MainThreadBody).detach(); 99 pw::Thread(log_thread_context, LoggingThreadBody).detach(); 100 101 // Start an app thread that uses the app_thread_context. Since the stack size 102 // is not specified, the full stack provided by app_thread_context is used. 103 app_thread = pw::Thread( 104 app_thread_context, pw::ThreadAttrs().set_name("app 1"), AppThreadBody1); 105 } 106 107 void MainThreadBody() { 108 // Join the "app 1" thread and reuse the app_thread_context for a new thread. 109 app_thread.join(); 110 app_thread = pw::Thread( 111 app_thread_context, pw::ThreadAttrs().set_name("app 2"), AppThreadBody2); 112 ... 113 } 114 115---------- 116Motivation 117---------- 118Pigweed's ``pw_thread`` module does not support cross-platform thread creation. 119Instead, threads must be created by instantiating a 120:cpp:class:`pw::thread::Options` specific to the thread backend. For example, to 121create a FreeRTOS thread, one must instantiate a 122:cpp:class:`pw::thread::freertos::Options` and configure it with a 123:cpp:class:`pw::thread::freertos::Context` 124 125Cross-platform thread creation was intentionally avoided in the ``pw_thread`` 126API. It is not possible to specify thread attributes in a truly generic, 127portable way. Every OS/RTOS exposes a different set of thread parameters, and 128settings for one platform may behave completely differently or not exist on 129another. 130 131Cross-platform thread creation may not be possible to do perfectly, but avoiding 132it has significant downsides. 133 134- The current APIs optimize for control at the expense of usability. Thread 135 creation is complex. 136- Developers always have to deal with the full complexity of thread creation, 137 even for simple cases or when just getting started. 138- Users must learn a slightly different API for each RTOS. The full ``Thread`` 139 API cannot be documented in one place. 140- Cross-platform code that creates threads must call functions that return 141 ``pw::thread::Options``. Each platform implements the functions as needed. 142 This requires exposing threads in the public API. Libraries such as 143 :ref:`module-pw_system` cannot add internal threads without breaking their 144 users. 145- Code for creating ``pw::thread::Options`` must be duplicated for each 146 platform. 147- Projects avoid writing cross-platform code and tests due to the complexity of 148 thread creation. 149 150``pw_system`` and threads 151========================= 152Currently, running :ref:`module-pw_system` requires writing custom low-level 153code that is aware of both ``pw_system`` and the RTOS it is running on 154(see e.g. `boot.cc 155<https://cs.opensource.google/pigweed/pigweed/+/4d23123c37a33638b2f1ce611423e74d385623ff:targets/stm32f429i_disc1_stm32cube/boot.cc;l=133>`_ 156and `target_hooks.cc 157<https://cs.opensource.google/pigweed/pigweed/+/4d23123c37a33638b2f1ce611423e74d385623ff:pw_system/zephyr_target_hooks.cc>`_). 158Enabling cross-platform thread creation would make it easier to use 159``pw_system``. The code for running ``pw_system`` on any target would be the 160same: a single function call in ``main``. The user would no longer have to 161allocate stacks or create :cpp:class:`pw::thread::Options` for ``pw_system`` 162threads; this could be managed by ``pw_system`` itself and configured with 163generic ``pw_system`` options if needed. 164 165Cross-platform thread creation also makes it easier for ``pw_system`` users to 166write their own code. Setting up a thread takes just two lines of code and no 167interactions with RTOS-specific APIs. A ``pw_system`` application created this 168way can run on any platform out of the box. 169 170--------------------- 171Problem investigation 172--------------------- 173Various cross-platform threading APIs exist today. 174 175C++ Standard Library 176==================== 177The C++ Standard Library currently provides a limited cross-platform thread 178creation API in ``<thread>``. No thread attributes are exposed; threads are 179created with platform defaults. 180 181An effort is underway to standardize some thread attributes, giving users more 182control over threads while maintaining portability. See `P2019 -- Thread 183attributes 184<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2019r6.pdf>`_ for 185details. The latest proposal exposes the thread name and stack size. Some 186alternatives have also been proposed (`P3072 187<https://open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3072r2.html>`_). 188 189POSIX 190===== 191POSIX is a portable operating system API. The POSIX thread creation function 192``pthread_create`` takes a pointer to a ``pthread_attr_t`` struct. This struct 193may a support a wide variety thread options that are configured with functions 194such as ``pthread_attr_setstacksize``, ``pthread_attr_setschedpolicy``, and 195others. A thread's name can be set with ``pthread_setname_np``. See `man 196pthreads <https://man7.org/linux/man-pages/man7/pthreads.7.html>`_ for details. 197 198CMSIS-RTOS 199========== 200The `CMSIS-RTOS2 API 201<https://www.keil.com/pack/doc/CMSIS/RTOS2/html/index.html>`_ provides a generic 202RTOS interface intended for use with Arm Cortex devices. CMSIS-RTOS2 is 203implemented by several operating systems, including FreeRTOS and Arm's own Keil 204RTX5. 205 206CMSIS-RTOS2 provides a comprehensive set of thread attributes in its 207`osThreadAttr_t 208<https://www.keil.com/pack/doc/CMSIS/RTOS2/html/group__CMSIS__RTOS__ThreadMgmt.html#structosThreadAttr__t>`_ 209struct. It also provides functions for initializing and controlling the 210scheduler, such as `osKernelStart 211<https://www.keil.com/pack/doc/CMSIS/RTOS2/html/group__CMSIS__RTOS__KernelCtrl.html#ga9ae2cc00f0d89d7b6a307bba942b5221>`_. 212 213-------- 214Proposal 215-------- 216The new cross-platform API does not replace the existing backend-specific thread 217creation APIs. The new API supports most production use cases, but does not 218expose the full capabilities and configuration of all supported RTOSes. It is 219intended to be easy to adopt, while providing a frictionless pathway to the 220current, fully configurable APIs if needed. 221 222With this proposal, per-target thread creation is simply a matter of setting 223variables differently for each target. This removes the need for duplicated code 224for creating platform-specific thread contexts and ``pw::thread::Options``. 225 226Generic thread attributes 227========================= 228This SEED introduces a limited set of cross-platform thread attributes. These 229generic attributes map to a platform-specific :cpp:class:`pw::thread::Options`. 230 231There are three thread attributes: 232 233- Name 234- Stack size 235- Priority 236 237Other attributes may be added in the future, such as dynamic or static 238resource allocation. 239 240Thread attributes are provided only as hints to the backend. Backends should 241respect thread attributes, if possible, but may ignore or adapt them depending 242on the OS's capabilities. Backends cannot fail to create thread because of how 243thread attributes are set, but users may check the backend's capabilities, such 244as whether thread priorities are supported, as needed. 245 246Examples of acceptable adaptations to thread attributes. 247 248- Ignore the thread name and stack size because the underlying API does not 249 support specifying them (e.g. C++'s ``<thread>``). 250- Silently truncate a thread name because the underlying RTOS only supports 251 shorter names. 252- Round up to the minimum required stack size from a smaller requested stack 253 size. 254- Add a fixed amount to a requested stack size to account for RTOS overhead. 255- Dynamically allocate the thread stack if it is above a certain size; 256 statically allocate it otherwise. 257 258.. _seed-0128-thread-config-survey: 259 260Why these thread attributes? 261---------------------------- 262A survey of thread creation with Pigweed across a few large, production projects 263found that 99% of their thread configurations can be exactly represented with 264thread name, priority, stack size. The only exception was a single RTOS feature 265used in a few threads in one project. 266 267The proof is in the pudding: ``pw_thread`` users almost never need low-level, 268RTOS-specific threading features. Abstracting these three thread attributes 269dramatically simplifies thread creation, resulting in more portable, 270easier-to-test code. In the rare cases when more control is needed, the existing 271non-portable ``pw_thread`` API is ready to use. 272 273OS / RTOS support for thread attributes 274--------------------------------------- 275Most OS APIs support the proposed thread attributes. 276 277.. list-table:: 278 :header-rows: 1 279 280 * - OS / API 281 - function 282 - name 283 - stack size 284 - priority type 285 - priority levels 286 * - C++ ``<thread>`` 287 - `std::thread <https://en.cppreference.com/w/cpp/thread/thread/thread>`_ 288 - none 289 - none 290 - none 291 - none 292 * - POSIX 293 - `pthread_create 294 <https://man7.org/linux/man-pages/man3/pthread_create.3.html>`_ 295 - `C string 296 <https://man7.org/linux/man-pages/man3/pthread_setname_np.3.html>`_ 297 - `bytes 298 <https://man7.org/linux/man-pages/man3/pthread_attr_setstacksize.3.html>`_ 299 - `pthread_attr_setschedparam <https://man7.org/linux/man-pages/man3/pthread_attr_setschedparam.3.html>`_ 300 - `at least 32 301 <https://man7.org/linux/man-pages/man2/sched_get_priority_max.2.html>`_ 302 * - `CMSIS-RTOS2 / Keil RTX5 <https://arm-software.github.io/CMSIS_6/latest/RTOS2/group__CMSIS__RTOS__ThreadMgmt.html>`_ 303 - `osThreadNew <https://arm-software.github.io/CMSIS_6/latest/RTOS2/group__CMSIS__RTOS__ThreadMgmt.html#ga48d68b8666d99d28fa646ee1d2182b8f>`_ 304 - `C string 305 <https://arm-software.github.io/CMSIS_6/latest/RTOS2/group__CMSIS__RTOS__ThreadMgmt.html#structosThreadAttr__t>`__ 306 - bytes 307 - `osPriority_t 308 <https://arm-software.github.io/CMSIS_6/latest/RTOS2/group__CMSIS__RTOS__ThreadMgmt.html#gad4e3e0971b41f2d17584a8c6837342ec>`_ 309 - 56 310 * - `embOS <https://www.segger.com/downloads/embos/UM01001>`_ 311 - ``OS_TASK_Create()`` 312 - | C string 313 | uses pointer 314 - bytes 315 - ``unsigned int`` 316 - 2³²-2 317 * - `FreeRTOS <https://www.freertos.org>`_ 318 - `xTaskCreateStatic <https://www.freertos.org/xTaskCreateStatic.html>`_ 319 - | C string 320 | copies `configMAX_TASK_NAME_LEN <https://www.freertos.org/a00110.html#configMAX_TASK_NAME_LEN>`_ 321 - words 322 - `unsigned int <https://www.freertos.org/RTOS-task-priority.html>`_ 323 - | `configMAX_PRIORITIES <https://www.freertos.org/a00110.html#configMAX_PRIORITIES>`_ 324 | `≤32 in some configs <https://www.freertos.org/a00110.html#configUSE_PORT_OPTIMISED_TASK_SELECTION>`_ 325 * - `NuttX <https://nuttx.apache.org/docs/latest/index.html>`_ 326 - | `task_create <https://nuttx.apache.org/docs/latest/reference/user/01_task_control.html#c.task_create>`_ 327 | (also POSIX APIs) 328 - C string 329 - bytes 330 - ``int`` 331 - `256 <https://github.com/apache/nuttx/blob/0ed714bba4280f98f35cb0df1f9d668099604f97/include/sys/types.h#L81>`_ 332 * - `ThreadX <https://github.com/eclipse-threadx/rtos-docs>`_ 333 - `tx_thread_create 334 <https://github.com/eclipse-threadx/rtos-docs/blob/80bd9fe9a33fa79257c75629be1b4438b84db7bc/rtos-docs/threadx/chapter4.md#tx_thread_create>`_ 335 - `C string 336 <https://github.com/eclipse-threadx/rtos-docs/blob/80bd9fe9a33fa79257c75629be1b4438b84db7bc/rtos-docs/threadx/chapter4.md#example-54>`__ 337 - bytes 338 - ``unsigned int`` (``TX_MAX_PRIORITIES - 1``)–0 (0 highest) 339 - `multiple of 32 340 <https://github.com/eclipse-threadx/threadx/blob/80bd9fe9a33fa79257c75629be1b4438b84db7bc/common/inc/tx_api.h#L2143>`_ 341 * - ``pw::ThreadContext`` 342 - :cpp:type:`pw::Thread` 343 - C string 344 - bytes 345 - custom class 346 - same as underying OS 347 348Creating threads 349================ 350The APIs proposed in this SEED streamline thread creation for common use cases, 351while allowing for full configuration when necessary. 352 353Generally, projects should start with the minimum complexity required and 354increase the complexity only if more control is needed. Threads defined in 355upstream Pigweed should start with some configurability to avoid friction in 356downstream projects. 357 358Dynamic threads: "just give me a thread" 359---------------------------------------- 360For simple cases, Pigweed will offer a new static ``pw::Thread::Start`` 361function. 362 363.. code-block:: c++ 364 365 #include "pw_thread/thread.h" 366 367 void CreateThreads() { 368 pw::Thread::Start([] { /* thread body */ ).detach(); 369 } 370 371.. admonition:: When should I use ``pw::Thread::Start``? 372 373 - Experimenting 374 - Prototyping 375 376Declare a default thread 377------------------------ 378Create a thread with ``DefaultThreadContext`` and default attributes. The 379``pw_thread`` backend starts a thread with a default name, stack size, and 380priority. 381 382.. code-block:: c++ 383 384 #include "pw_thread/thread.h" 385 386 pw::DefaultThreadContext context; 387 388 void CreateThreads() { 389 pw::Thread(context, pw::ThreadAttrs(), [] { /* thread body */ }).detach(); 390 } 391 392.. admonition:: When should I use default thread contexts? 393 394 - Experimenting 395 - Prototyping 396 - Testing 397 - Getting started 398 399Configurable thread attributes 400------------------------------ 401Define a ``pw::ThreadAttrs`` and use it to create threads with 402``pw::ThreadContext<>``. Attributes are configured as needed using the project's 403configuration pattern. 404 405.. code-block:: c++ 406 407 #include "pw_thread/thread.h" 408 #include "project/config.h" 409 410 constexpr auto kMyThread = pw::ThreadAttrs() 411 .set_name("my thread") 412 .set_priority(MY_THREAD_PRIORITY) 413 .set_stack_size_bytes(kMyThreadStackSizeBytes); 414 415 pw::ThreadContext<kMyThread> my_thread_context; 416 417 pw::Thread other_thread; 418 pw::ThreadContext<kOtherThreadStackSizeBytes> other_thread_context; 419 420 void StartThreads() { 421 pw::Thread(my_thread_context, [] { /* thread body */ }).detach(); 422 423 other_thread = pw::Thread(other_thread_context, 424 pw::ThreadAttrs().set_name("other"), 425 OtherThreadBody); 426 } 427 428Example configuration header: 429 430.. code-block:: c++ 431 432 // "project/config.h" 433 434 // Configurable thread priority. Can be changed by defining 435 // MY_THREAD_PRIORITY in the build system. 436 #ifndef MY_THREAD_PRIORITY 437 #define MY_THREAD_PRIORITY pw::ThreadPriority::High() 438 #endif // MY_THREAD_PRIORITY 439 440 // Configuration may be based on the target platform. 441 #if BUILDING_FOR_PLATFORM_A 442 inline constexpr size_t kMyThreadStackSizeBytes = 2048; 443 inline constexpr size_t kOtherThreadStackSizeBytes = 1024; 444 #else 445 inline constexpr size_t kMyThreadStackSizeBytes = 1536; 446 inline constexpr size_t kOtherThreadStackSizeBytes = 512; 447 #endif // BUILDING_FOR_PLATFORM_A 448 449.. admonition:: When should I use configurable thread attributes? 450 451 - Pigweed upstream development 452 - Production project development 453 454Platform-specific thread creation 455--------------------------------- 456In the rare case that platform-specific thread configuration is required, 457provide a function that returns ``NativeOptions`` or ``const Options&`` and use 458it to create a thread. The function may be a facade, so each target can 459implement it differently. Projects may provide a default implementation of the 460function that uses ``pw::ThreadAttrs``. 461 462This approach is equivalent to the original non-portable ``pw_thread`` creation 463pattern, optionally with a ``pw::ThreadAttrs``-based default implementation of 464the function. This approach is only necessary for threads that specifically 465require non-portable features. Other threads should continue to use 466``pw::ThreadAttrs``. 467 468.. code-block:: c++ 469 470 #include "pw_thread/thread.h" 471 #include "project/config.h" 472 473 // This function returns a `pw::thread::Options` for creating a thread. 474 pw::thread::NativeOptions GetThreadOptions(); 475 476 // Optionally, provide a default implementation of `GetThreadOptions()` that 477 // uses `pw::ThreadAttrs`. 478 #if !PROJECT_CFG_THREAD_CUSTOM_OPTIONS 479 480 pw::thread::NativeOptions GetThreadOptions() { 481 static constinit pw::ThreadContext<project::cfg::kThreadStackSizeHintBytes> context; 482 return pw::thread::GetNativeOptions( 483 context, pw::ThreadAttrs().set_name("thread name")); 484 } 485 486 #endif // !PROJECT_CFG_THREAD_CUSTOM_OPTIONS 487 488 // Call `GetThreadOptions()` to create a thread. 489 void CreateThreads() { 490 pw::Thread(GetThreadOptions(), [] { /* thread body */ }).detach(); 491 } 492 493Example configuration header: 494 495.. code-block:: c++ 496 497 // project/config.h 498 499 // Set to 1 to implement `GetThreadOptions()` and provide fully custom 500 // `pw::thread::Options` for the platform. 501 #ifndef PROJECT_CFG_THREAD_CUSTOM_OPTIONS 502 #define PROJECT_CFG_THREAD_CUSTOM_OPTIONS 0 503 #endif // PROJECT_CFG_THREAD_CUSTOM_OPTIONS 504 505 // Stack size setting for the default thread options. 506 #ifndef PROJECT_CFG_THREAD_STACKS_SIZE_HINT 507 #define PROJECT_CFG_THREAD_STACKS_SIZE_HINT 2048 508 #endif // PROJECT_CFG_THREAD_STACKS_SIZE_HINT 509 510 namespace project::cfg { 511 512 inline constexpr size_t kThreadStackSizeHintBytes = PROJECT_CFG_THREAD_STACKS_SIZE_HINT; 513 514 } // namespace project::cfg 515 516This approach is not recommended as a starting point. It adds complexity that is 517unlikely to be necessary. Most projects should start with configurable 518``ThreadAttrs`` and add switch to platform-specific thread configuration only 519for threads that need it. 520 521.. admonition:: When should I use platform-specific thread creation? 522 523 - Pigweed upstream development, if a downstream user specifically requires 524 platform-specific thread features for a thread defined by Pigweed. 525 - Production project development that requires platform-specific thread 526 features. 527 528C++ implementation details 529========================== 530 531Facade additions 532----------------- 533This proposal adds a few items to the ``pw_thread`` facade: 534 535- Aliases for the native context types wrapped by ``pw::ThreadContext``. 536- Information about the range of supported thread priorities used by 537 ``pw::ThreadPriority``. 538- Alias for the native ``pw::thread::Options`` type. 539- Function that maps ``pw::ThreadContext`` and ``pw::ThreadAttrs`` to native 540 ``pw::thread::Options``. 541 542These features are used by ``pw_thread`` classes, not end users. 543 544.. code-block:: c++ 545 546 // pw_thread_backend/thread_native.h 547 548 namespace pw::thread::backend { 549 550 // Native, non-templated context (resources). 551 using NativeContext = /* implementation-defined */; 552 553 // Thread context with a stack size hint. Must derive from or be the same 554 // type as `NativeContext`. Must be default constructible. 555 template <size_t kStackSizeHintBytes> 556 using NativeContextWithStack = /* implementation-defined */; 557 558 // Stack size to use when unspecified; 0 for platforms that do not support 559 // defining the stack size. 560 inline constexpr size_t kDefaultStackSizeBytes = /* implementation-defined */; 561 562 // Define the range of thread priority values. These values may represent a 563 // subset of priorities supported by the OS. The `kHighestPriority` may be 564 // numerically higher or lower than `kLowestPriority`, depending on the OS. 565 // Backends that do not support priorities must set `kLowestPriority` and 566 // `kHighestPriority` to the same value, and should use `int` for 567 // `NativePriority`. 568 using NativePriority = /* implementation-defined */; 569 inline constexpr NativePriority kLowestPriority = /* implementation-defined */; 570 inline constexpr NativePriority kHighestPriority = /* implementation-defined */; 571 572 // Native options class derived from pw::thread::Options. 573 using NativeOptions = /* implementation-defined */; 574 575 // Converts cross-platform ThreadAttrs to NativeOptions. May be defined 576 // in ``pw_thread_backend/thread_inline.h`` or in a .cc file. 577 NativeOptions GetNativeOptions(NativeContext& context, 578 const ThreadAttrs& attributes); 579 580 } // namespace pw::thread::backend 581 582``pw_thread_stl`` example implementation: 583 584.. code-block:: c++ 585 586 namespace pw::thread::backend { 587 588 using NativeContext = pw::thread::stl::Context; 589 590 // Ignore the stack size since it's not supported. 591 template <size_t> 592 using NativeContextWithStack = pw::thread::stl::Context; 593 594 inline constexpr size_t kDefaultStackSizeBytes = 0; 595 596 using NativePriority = int; 597 inline constexpr NativePriority kLowestPriority = 0; 598 inline constexpr NativePriority kHighestPriority = 0; 599 600 using NativeOptions = pw::thread::stl::Options; 601 602 inline NativeOptions GetNativeOptions(NativeContext&, const ThreadAttrs&) { 603 return pw::thread::stl::Options(); 604 } 605 606 } // namespace pw::thread::backend 607 608``pw_thread_freertos`` example implementation: 609 610.. code-block:: c++ 611 612 namespace pw::thread::backend { 613 614 using NativeContext = pw::thread::freertos::StaticContext; 615 616 // Convert bytes to words, rounding up. 617 template <size_t kStackSizeBytes> 618 using NativeContextWithStack = pw::thread::stl::StaticContextWithStack< 619 (kStackSizeBytes + sizeof(StackType_t) - 1) / sizeof(StackType_t)>; 620 621 inline constexpr size_t kDefaultStackSizeBytes = 622 pw::thread::freertos::config::kDefaultStackSizeWords; 623 624 using NativePriority = UBaseType_t; 625 inline constexpr NativePriority kLowestPriority = tskIDLE_PRIORITY; 626 inline constexpr NativePriority kHighestPriority = configMAX_PRIORITIES - 1; 627 628 using NativeOptions = pw::thread::freertos::Options; 629 630 inline NativeOptions GetNativeOptions(NativeContext& context, 631 const ThreadAttrs& attrs) { 632 return pw::thread::freertos::Options() 633 .set_static_context(context), 634 .set_name(attrs.name()) 635 .set_priority(attrs.priority().native()) 636 } 637 638 } // namespace pw::thread::backend 639 640``ThreadPriority`` 641------------------ 642Different OS APIs define priorities very differently. Some support a few 643priority levels, others support the full range of a ``uint32_t``. For some, 0 is 644the lowest priority and for others it is the highest. And changing the OS's 645scheduling policy might changes how threads are scheduled without changing their 646priorities. 647 648``pw::ThreadPriority`` represents thread priority precisely but abstractly. It 649supports the following: 650 651- Represent the full range of priorities supported by the underlying OS. 652- Set priorities in absolute terms that map to OS priority ranges in a 653 reasonable way. 654- Set priorities relative to one another. 655- Check that priorities are actually higher or lower than one another on a given 656 platform at compile time. 657- Check if the backend supports thread priorities at all. 658 659Many projects will be able to define a single priority set for all platforms. 660The priorities may translate differently to each platforms, but this may not 661matter. If a single set of priorities does not work for all platforms, 662priorities can be configured per platform, like other attributes. 663 664Here is a high-level overview of the class: 665 666.. code-block:: c++ 667 668 namespace pw { 669 670 class ThreadPriority { 671 public: 672 // True if the backend supports different priority levels. 673 static constexpr bool IsSupported(); 674 675 // Named priorities. These priority levels span the backend's supported 676 // priority range. 677 // 678 // The optional `kPlus` template parameter returns a priority the specified 679 // number of levels higher than the named priority, but never exceeding the 680 // priority of the next named level, if supported by the backend. 681 static constexpr ThreadPriority VeryLow<unsigned kPlus = 0>(); 682 static constexpr ThreadPriority Low<unsigned kPlus = 0>(); 683 static constexpr ThreadPriority MediumLow<unsigned kPlus = 0>(); 684 static constexpr ThreadPriority Medium<unsigned kPlus = 0>(); 685 static constexpr ThreadPriority MediumHigh<unsigned kPlus = 0>(); 686 static constexpr ThreadPriority High<unsigned kPlus = 0>(); 687 static constexpr ThreadPriority VeryHigh<unsigned kPlus = 0>(); 688 689 // Refers to the lowest or highest priority supported by the OS. 690 static constexpr ThreadPriority Lowest<unsigned kPlus = 0>(); 691 static constexpr ThreadPriority Highest(); 692 693 // Returns the ThreadPriority with next distinct higher or lower value. If 694 // the priority is already the highest/lowest, returns the same value. 695 constexpr ThreadPriority NextLower(); 696 constexpr ThreadPriority NextHigher(); 697 698 // Returns the ThreadPriority with next distinct higher or lower value. 699 // Asserts that the priority is not already the highest/lowest. 700 constexpr ThreadPriority NextLowerChecked(); 701 constexpr ThreadPriority NextHigherChecked(); 702 703 // ThreadPriority supports comparison. This makes it possible, for example, 704 // to static_assert that one priority is higher than another in the 705 // backend. 706 constexpr bool operator==(const ThreadPriority&); 707 ... 708 709 // Access the native thread priority type. These functions may be helpful 710 // when ThreadPriority is configured separately for each platform. 711 using native_type = backend::NativeThreadPriority; 712 713 static constexpr FromNative(native_type native_priority); 714 715 native_type native() const; 716 }; 717 718 } // namespace pw 719 720Example uses: 721 722.. code-block:: c++ 723 724 // Named priorities are spread over the backend's supported priority range. 725 constexpr pw::ThreadPriority kThreadOne = ThreadPriority::Low(); 726 constexpr pw::ThreadPriority kThreadTwo = ThreadPriority::Medium(); 727 728 // Define a priority one higher than Medium, but never equal to or greater 729 // than the next named priority, MediumHigh, if possible in the given 730 // backend. 731 constexpr pw::ThreadPriority kThreadThree = ThreadPriority::Medium<1>(); 732 733 // Set the priority exactly one backend priority level higher than 734 // kThreadThree, if supported by the backend. 735 constexpr pw::ThreadPriority kThreadFour = kThreadThree.NextHigher(); 736 737 static_assert(!ThreadPriority::IsSupported() || kThreadThree < kThreadFour); 738 739.. tip:: 740 741 It is recommended that projects pick a starting priority level (e.g. 742 ``ThreadPriority::Lowest().NextHigher()``) and define all priorities relative 743 to it. 744 745Mapping OS priorities to named priorities 746^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 747If thread priorities are not supported, all named priorities are the same level. 748 749If fewer than 7 levels are supported by the backend, some named levels map to 750the same OS priority. For example, if there are only 3 priority levels 751supported, then ``VeryLow == Low``, ``MediumLow == Medium == MediumHigh``, and 752``High == VeryHigh``. 753 754For backends that support 7 or more priority levels, each named priority level 755is guaranteed to map to a unique OS priority. 756 757``ThreadAttrs`` 758--------------- 759The ``ThreadAttrs`` class represents generic thread attributes. It is a 760cross-platform version of :cpp:class:`pw::thread::Options`. 761 762.. code-block:: c++ 763 764 namespace pw { 765 766 // Generic thread attributes. 767 class ThreadAttrs { 768 public: 769 // Initializes ThreadAttrs to their backend-defined defaults. 770 constexpr ThreadAttrs(); 771 772 // ThreadAttrs can be copied to share properties between threads. 773 constexpr ThreadAttrs(const ThreadAttrs&) = default; 774 constexpr ThreadAttrs& operator=(const ThreadAttrs&) = default; 775 776 // Name hint as a null-terminated string; never null. 777 constexpr const char* name() const; 778 constexpr ThreadAttrs& set_name(const char* name); 779 780 constexpr Priority priority() const; 781 constexpr ThreadAttrs& set_priority(Priority priority); 782 783 // Increment or decrement the priority to set task priorities relative to 784 // one another. 785 constexpr ThreadAttrs& set_priority_next_higher(); 786 constexpr ThreadAttrs& set_priority_next_lower(); 787 788 constexpr size_t stack_size_bytes() const; 789 constexpr ThreadAttrs& set_stack_size_bytes(size_t stack_size_bytes); 790 }; 791 792 } // namespace pw 793 794``ThreadAttrs`` may be defined at runtime or as ``constexpr`` constants. 795Projects may find it helpful to define ``ThreadAttrs`` in a centralized 796location. 797 798.. code-block:: c++ 799 800 #include "pw_thread/attrs.h" 801 #include "my_project/config.h" 802 803 namespace my_project { 804 805 // Global list of thread attributes. 806 807 inline constexpr auto kThreadOne = pw::ThreadAttrs() 808 .set_name("thread one") 809 .set_stack_size_bytes(1024) 810 .set_priority(pw::ThreadPriority::Medium()); 811 812 inline constexpr auto kThreadTwo = pw::ThreadAttrs(kThreadOne) 813 .set_name("thread two"); 814 815 inline constexpr auto kImportantThread = pw::ThreadAttrs() 816 .set_name("important!") 817 .set_stack_size_bytes(IMPORTANT_THREAD_STACK_SIZE_BYTES) 818 .set_priority(IMPORTANT_THREAD_PRIORITY); 819 820 inline constexpr auto kLessImportantThread = pw::ThreadAttrs() 821 .set_name("also important!") 822 .set_stack_size_bytes(IMPORTANT_THREAD_STACK_SIZE_BYTES) 823 .set_priority(kImportantThread.priority().NextLower()); 824 825 static_assert( 826 !pw::ThreadPriority::IsSupported() || 827 kImportantThread.priority() > kLessImportantThread.priority(), 828 "If the platform supports priorities, ImportantThread must be higher " 829 "priority than LessImportantThread"); 830 831 } // namespace my_project 832 833``ThreadContext`` 834----------------- 835``pw::ThreadContext`` represents the resources required to run one thread. 836This may include platform-specific handles, a statically allocated thread 837control block (TCB), or the thread's stack. If platforms do not require manual 838allocation for threads, ``pw::ThreadContext`` may be empty. 839 840``ThreadContext`` is a generic wrapper around a backend-defined object. It 841prevents unintentional access of backend-specific features on the native object. 842 843``ThreadContext`` objects may be reused if their associated thread has been 844joined. 845 846``ThreadContext`` takes a few forms: 847 848- ``ThreadContext<kStackSizeHintBytes>`` -- Context with internally allocated 849 thread stack. 850- ``ThreadContext<kThreadAttrs>`` -- Context associated with a set of 851 ``ThreadAttrs``. Uses internally or externally allocated stack based on the 852 ``ThreadAttrs``. 853- ``ThreadContext<>`` -- Context with a runtime-provided ``ThreadStack``. 854 855.. code-block:: c++ 856 857 namespace pw { 858 859 // Represents the resources required for one thread. May include OS data 860 // structures, the thread stack, or be empty, depending on the platform. 861 // 862 // ThreadContext may be reused or deleted if the associated thread is 863 // joined. 864 template <auto> 865 class ThreadContext; 866 867 // ThreadContext with integrated stack. 868 template <size_t kStackSizeHintBytes, 869 size_t kAlignmentBytes = alignof(std::max_align_t)> 870 class ThreadContext { 871 public: 872 constexpr ThreadContext() = default; 873 874 private: 875 backend::NativeContextWithStack<kStackSizeHintBytes, kAlignmentBytes> native_context_; 876 }; 877 878 // Alias for ThreadContext with the backend's default stack size. 879 using DefaultThreadContext = ThreadContext<backend::kDefaultStackSizeBytes>; 880 881 // Declares a ThreadContext that is associated with a specific set of thread 882 // attributes. Internally allocates the stack if the stack size hint is set. 883 // The ThreadContext may be reused if the associated thread is joined, but 884 // all threads use the same ThreadAttrs. 885 template <const ThreadAttrs& kAttributes> 886 class ThreadContext { 887 private: 888 ThreadContext<kAttributes.stack_size_bytes()> context_; 889 }; 890 891 } // namespace pw 892 893 #include "pw_thread_backend/thread_inline.h" 894 895``ThreadStack`` 896--------------- 897Represents a thread stack of the specified size. The object may be empty if the 898backends dynamically allocate stacks. 899 900.. code-block:: c++ 901 902 namespace pw { 903 904 template <size_t kStackSizeBytes> 905 class ThreadStack { 906 private: 907 backend::NativeThreadStack<kStackSizeBytes> native_stack_; 908 }; 909 910 } // namespace pw 911 912``ThreadStack`` may specified separately from the ``ThreadContext`` if users 913have need to declare stacks in different sections or want to keep them separate 914from other items in the ``ThreadContext``. The ``ThreadStack`` is set on the 915``ThreadAttrs`` instead of the stack size: 916 917.. code-block:: c++ 918 919 STACK_SECTION alignas(256) constinit ThreadStack<kAppStackSizeBytes> kMainStack; 920 921 constexpr pw::ThreadAttrs kMainThread = pw::ThreadAttrs() 922 .set_name("MainThread") 923 .set_stack(kMainStack) 924 .set_priority(kMainPriority); 925 926 ThreadContext<kMainThread> kMainThreadContext; 927 928 void RunThread() { 929 pw::Thread(kMainThreadContext, [] { /* thread body */ }).detach(); 930 } 931 932``ThreadContext`` objects that are not associated with a ``ThreadAttrs`` work 933similarly: 934 935.. code-block:: c++ 936 937 STACK_SECTION alignas(256) constinit ThreadStack<kAppStackSizeBytes> kAppStack; 938 939 ThreadContext<> kAppThreadContext; 940 941 void RunThreads() { 942 pw::Thread thread(kAppThreadContext, 943 pw::ThreadAttrs().set_stack(kAppStack).set_name("T1"), 944 [] { /* thread body */ }); 945 thread.join() 946 947 pw::Thread thread(kAppThreadContext, 948 pw::ThreadAttrs().set_stack(kAppStack).set_name("T2"), 949 [] { /* thread body */ }); 950 thread.join(); 951 } 952 953The ``STACK_SECTION`` macro would be provided by a config header: 954 955.. code-block:: c++ 956 957 #if BUILDING_FOR_DEVICE_A 958 #define STACK_SECTION PW_PLACE_IN_SECTION(".thread_stacks") 959 #else // building for device B 960 #define STACK_SECTION // section doesn't matter 961 #endif // BUILDING_FOR_DEVICE_A 962 963``Thread`` additions 964-------------------- 965``pw::Thread`` will accept ``ThreadContext`` and ``ThreadAttrs``. 966 967.. code-block:: c++ 968 969 class Thread { 970 // Existing constructor. 971 Thread(const Options& options, Function<void()>&& entry) 972 973 // Creates a thread with a ThreadContext associated with a ThreadAttrs. 974 template <const ThreadAttrs& kAttributes> 975 Thread(ThreadContext<kAttributes>& context, Function<void()>&& entry); 976 977 // Creates a thread from attributes passed in a template parameter. 978 template <const ThreadAttrs& kAttributes, size_t kStackSizeHintBytes> 979 Thread(ThreadContext<kStackSizeHintBytes>& context, 980 Function<void()>&& entry); 981 982 // Creates a thread from context and attributes. Performs a runtime check 983 // that the ThreadContext's stack is large enough, which can be avoided by 984 // using one of the other constructors. 985 template <size_t kStackSizeHintBytes> 986 Thread(ThreadContext<kStackSizeHintBytes>& context, 987 const ThreadAttrs& attributes, 988 Function<void()>&& entry); 989 990 // Creates a thread with the provided context and attributes. The 991 // attributes have a ThreadStack set. 992 Thread(ThreadContext<>& context, 993 const ThreadAttrs& attributes, 994 Function<void()>&& entry); 995 996Dynamic thread creation function 997-------------------------------- 998The ``pw::Thread::Start`` function starts a thread as simply as possible. It 999starts returns a ``pw::Thread`` that runs a user-provided function. Users may 1000optionally provide ``pw::ThreadAttrs``. 1001 1002``pw::Thread::Start`` is implemented with a new, separate facade. The backend 1003may statically or dynamically allocate resources. A default backend that 1004statically allocates resources for a fixed number of threads will be provided in 1005upstream Pigweed. 1006 1007.. code-block:: c++ 1008 1009 namespace pw { 1010 1011 class Thread { 1012 ... 1013 1014 // Starts running the thread_body in a separate thread. The thread is 1015 // allocated and managed by the backend. 1016 template <typename Function, typename... Args> 1017 static Thread Start(Function&& thread_body, Args&&... args); 1018 1019 template <typename Function, typename... Args> 1020 static Thread Start(const pw::ThreadAttrs& attributes, Function&& thread_body, Args&&... args); 1021 }; 1022 1023 } // namespace pw 1024