aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEwout van Bekkum <ewout@google.com>2021-04-15 14:36:37 -0700
committerCQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>2021-04-16 02:02:02 +0000
commita082d7fc6a0542c4e3c7361dd99b2362379f1685 (patch)
treee95ecd97d8ff1d876d7c5bad243ed7c114c17814
parentfaea880913e1abe88f02752bdceaf342abd28415 (diff)
downloadpigweed-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/BUILD12
-rw-r--r--pw_thread/BUILD.gn11
-rw-r--r--pw_thread/docs.rst194
-rw-r--r--pw_thread/public/pw_thread/thread.h73
-rw-r--r--pw_thread/public/pw_thread/thread_core.h48
-rw-r--r--pw_thread/thread.cc30
-rw-r--r--pw_thread/thread_facade_test.cc24
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