diff options
author | Ewout van Bekkum <ewout@google.com> | 2021-04-15 14:36:37 -0700 |
---|---|---|
committer | CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2021-04-16 02:02:02 +0000 |
commit | a082d7fc6a0542c4e3c7361dd99b2362379f1685 (patch) | |
tree | e95ecd97d8ff1d876d7c5bad243ed7c114c17814 | |
parent | faea880913e1abe88f02752bdceaf342abd28415 (diff) | |
download | pigweed-a082d7fc6a0542c4e3c7361dd99b2362379f1685.tar.gz |
pw_thread: add helper ThreadCore interface
Also extends the pw_thread documentation regarding thread creation.
Change-Id: Ib0a0572060188244b9a5cf91a6bba575057f405b
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/41205
Reviewed-by: Keir Mierle <keir@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
Reviewed-by: Paul Mathieu <paulmathieu@google.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
-rw-r--r-- | pw_thread/BUILD | 12 | ||||
-rw-r--r-- | pw_thread/BUILD.gn | 11 | ||||
-rw-r--r-- | pw_thread/docs.rst | 194 | ||||
-rw-r--r-- | pw_thread/public/pw_thread/thread.h | 73 | ||||
-rw-r--r-- | pw_thread/public/pw_thread/thread_core.h | 48 | ||||
-rw-r--r-- | pw_thread/thread.cc | 30 | ||||
-rw-r--r-- | pw_thread/thread_facade_test.cc | 24 |
7 files changed, 353 insertions, 39 deletions
diff --git a/pw_thread/BUILD b/pw_thread/BUILD index fb892c074..71957c076 100644 --- a/pw_thread/BUILD +++ b/pw_thread/BUILD @@ -101,8 +101,12 @@ pw_cc_library( name = "thread", deps = [ ":thread_facade", + ":thread_core", PW_THREAD_THREAD_BACKEND + "_headers", ], + srcs = [ + "thread.cc" + ], ) pw_cc_library( @@ -113,6 +117,14 @@ pw_cc_library( ) pw_cc_library( + name = "thread_core", + hdrs = [ + "public/pw_thread/thread_core.h", + ], + includes = ["public"], +) + +pw_cc_library( name = "yield_facade", hdrs = [ "public/pw_thread/yield.h", diff --git a/pw_thread/BUILD.gn b/pw_thread/BUILD.gn index a876828c3..ccd04f300 100644 --- a/pw_thread/BUILD.gn +++ b/pw_thread/BUILD.gn @@ -46,7 +46,16 @@ pw_facade("thread") { backend = pw_thread_THREAD_BACKEND public_configs = [ ":public_include_path" ] public = [ "public/pw_thread/thread.h" ] - public_deps = [ ":id" ] + public_deps = [ + ":id", + ":thread_core", + ] + sources = [ "thread.cc" ] +} + +pw_source_set("thread_core") { + public_configs = [ ":public_include_path" ] + public = [ "public/pw_thread/thread_core.h" ] } pw_facade("yield") { diff --git a/pw_thread/docs.rst b/pw_thread/docs.rst index af0f8f61c..03b1a5a27 100644 --- a/pw_thread/docs.rst +++ b/pw_thread/docs.rst @@ -1,8 +1,194 @@ .. _module-pw_thread: ---------- +========= pw_thread ---------- -This is a threading module for Pigweed. It is not ready for use, and is under -construction. +========= +The ``pw_thread`` module contains utilities for thread creation and thread +execution. +.. contents:: + :local: + :depth: 2 + +.. Warning:: + This module is still under construction, the API is not yet stable. + +--------------- +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. + +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 + - Planned + * - STL + - :ref:`module-pw_thread_stl` + * - Zephyr + - Planned + * - CMSIS-RTOS API v2 & RTX5 + - Planned + + +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. + +.. Note:: + Options have a default constructor, however default options are not portable! + Default options can only work if threads are dynamically allocated by default, + meaning default options cannot work on backends which require static thread + allocations. In addition on some schedulers, default options will not work + for other reasons. + +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! + +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! diff --git a/pw_thread/public/pw_thread/thread.h b/pw_thread/public/pw_thread/thread.h index d4632d5ee..f609fedc0 100644 --- a/pw_thread/public/pw_thread/thread.h +++ b/pw_thread/public/pw_thread/thread.h @@ -1,4 +1,4 @@ -// Copyright 2020 The Pigweed Authors +// Copyright 2021 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of @@ -14,6 +14,7 @@ #pragma once #include "pw_thread/id.h" +#include "pw_thread/thread_core.h" // clang-format off // The backend's thread_native header must provide PW_THREAD_JOINING_ENABLED. @@ -43,10 +44,10 @@ class Options { constexpr Options() = default; }; -// The class Thread represents a single thread of execution. Threads allow +// The class Thread can represent a single thread of execution. Threads allow // multiple functions to execute concurrently. // -// Threads begin execution immediately upon construction of the associated +// 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 @@ -71,44 +72,50 @@ class Thread { // Creates a new thread object which represents a thread of execution. // // Thread functions are permitted to return and must have the following - // signature: + // ThreadRoutine signature: // void example_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 { - // 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(invoke_foo_do_bar, &foo); - // thread.detach(); - // - // Postcondition: The thread get EITHER detached or joined. Note that this can - // sometimes be done through backend specific options during construction. - // - // WARNING: Options have a default constructor, however default options are - // not portable! Default options can only work if threads are dynamically + // 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 a helper ThreadCore interface can be implemented by an object + // so that a static lambda closure or function is not needed to dispatch to + // a member function without arguments. For example: + // class Foo : public ThreadCore { + // private: + // void Run() override {} + // }; + // Foo foo; + // + // // Now create the thread, using foo directly. + // Thread(options, foo).detach(); + // + // Postcondition: The thread get EITHER detached or joined. + // + // NOTE: Options have a default constructor, however default options are not + // portable! Default options can only work if threads are dynamically // allocated by default, meaning default options cannot work on backends which - // require static thread allocations. - // - // TODO(ewout): use a pw::Closure like object which supports the current API - // capability but with type safety and ease of executing member functions. - // We may consider a size constrained version of Zircon's fit's movable - // closure concept, which does not rely on dynamic allocation unlike - // std::function. Note that the plan is NOT to support any arbitrary number of - // arguments. + // require static thread allocations. In addition on some schedulers + // default options may not work for other reasons. using ThreadRoutine = void (*)(void* arg); Thread(const Options& options, ThreadRoutine entry, void* arg = nullptr); + Thread(const Options& options, ThreadCore& thread_core); // Postcondition: The other thread no longer represents a thread of execution. Thread& operator=(Thread&& other); diff --git a/pw_thread/public/pw_thread/thread_core.h b/pw_thread/public/pw_thread/thread_core.h new file mode 100644 index 000000000..23e4c6232 --- /dev/null +++ b/pw_thread/public/pw_thread/thread_core.h @@ -0,0 +1,48 @@ +// Copyright 2021 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +#pragma once + +namespace pw::thread { + +// An optional virtual interface which can be implemented by objects which are +// a thread as a helper to use pw::thread::Thread. +// +// This wrapper means that the user is not required to provide the indirection +// callback to call run based on the passed context. For example instead of: +// +// static auto invoke_foo_start = [](void *void_foo_ptr) { +// static_cast<Foo*>(void_foo_ptr)->Start(); +// }; +// Thread thread(options, invoke_foo_start, &foo).detach(); +// +// You can instead use the helper constructor in Thread: +// +// Thread 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! +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 diff --git a/pw_thread/thread.cc b/pw_thread/thread.cc new file mode 100644 index 000000000..d2844e583 --- /dev/null +++ b/pw_thread/thread.cc @@ -0,0 +1,30 @@ +// Copyright 2021 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#include "pw_thread/thread.h" + +namespace pw::thread { +namespace { + +void StartThreadCore(void* void_thread_core_ptr) { + static_cast<ThreadCore*>(void_thread_core_ptr)->Start(); +} + +} // namespace + +// Delegating constructor which defers to the facade's constructor. +Thread::Thread(const Options& options, ThreadCore& thread_core) + : Thread(options, StartThreadCore, &thread_core) {} + +} // namespace pw::thread diff --git a/pw_thread/thread_facade_test.cc b/pw_thread/thread_facade_test.cc index 0e9cf6862..008e2cacd 100644 --- a/pw_thread/thread_facade_test.cc +++ b/pw_thread/thread_facade_test.cc @@ -1,4 +1,4 @@ -// Copyright 2020 The Pigweed Authors +// Copyright 2021 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of @@ -141,5 +141,27 @@ TEST(Thread, MoveOperator) { WaitUntilDetachedThreadsCleanedUp(); } +class SemaphoreReleaser : public ThreadCore { + public: + pw::sync::BinarySemaphore& semaphore() { return semaphore_; } + + private: + void Run() override { semaphore_.release(); } + + sync::BinarySemaphore semaphore_; +}; + +TEST(Thread, ThreadCore) { + SemaphoreReleaser semaphore_releaser; + Thread thread(TestOptionsThread0(), semaphore_releaser); + EXPECT_NE(thread.get_id(), Id()); + EXPECT_TRUE(thread.joinable()); + thread.detach(); + EXPECT_EQ(thread.get_id(), Id()); + EXPECT_FALSE(thread.joinable()); + semaphore_releaser.semaphore().acquire(); + + WaitUntilDetachedThreadsCleanedUp(); +} } // namespace } // namespace pw::thread |