pw_thread: adds thread creation
Adds a std::thread like API through a new pw_thread facade based
on the STL's std::thread API, however with more restricted function
entry routine format support and backend specific option support
to permit non-portable configuration of thread settings/parameters
including static context allocations.
In addition this provides an initial set of backends based on using
the STL's std::thread directly.
Change-Id: Ib8c3cbdc434044e204e67e58d861a40e4acec9b4
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/30920
Reviewed-by: Wyatt Hepler <hepler@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Ewout van Bekkum <ewout@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index ae068a3..1399035 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -304,6 +304,7 @@
"$dir_pw_string:tests",
"$dir_pw_sync:tests",
"$dir_pw_thread:tests",
+ "$dir_pw_thread_stl:tests",
"$dir_pw_tokenizer:tests",
"$dir_pw_trace:tests",
"$dir_pw_trace_tokenized:tests",
diff --git a/pw_thread/BUILD b/pw_thread/BUILD
index 4335978..073897c 100644
--- a/pw_thread/BUILD
+++ b/pw_thread/BUILD
@@ -25,6 +25,7 @@
# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
PW_THREAD_ID_BACKEND = "//pw_thread_stl:id"
PW_THREAD_SLEEP_BACKEND = "//pw_thread_stl:sleep"
+PW_THREAD_THREAD_BACKEND = "//pw_thread_stl:thread"
PW_THREAD_YIELD_BACKEND = "//pw_thread_stl:yield"
pw_cc_library(
@@ -85,6 +86,33 @@
)
pw_cc_library(
+ name = "thread_facade",
+ hdrs = [
+ "public/pw_thread/thread.h",
+ ],
+ includes = ["public"],
+ deps = [
+ ":id_facade",
+ PW_THREAD_THREAD_BACKEND + "_headers",
+ ],
+)
+
+pw_cc_library(
+ name = "thread",
+ deps = [
+ ":thread_facade",
+ PW_THREAD_THREAD_BACKEND + "_headers",
+ ],
+)
+
+pw_cc_library(
+ name = "thread_backend",
+ deps = [
+ PW_THREAD_THREAD_BACKEND,
+ ],
+)
+
+pw_cc_library(
name = "yield_facade",
hdrs = [
"public/pw_thread/yield.h",
@@ -114,6 +142,32 @@
],
)
+pw_cc_library(
+ name = "test_threads_header",
+ hdrs = [
+ "public/pw_thread/test_threads.h",
+ ],
+ deps = [
+ ":thread",
+ ],
+)
+
+# To instantiate this as a pw_cc_test, depend on this pw_cc_library and the
+# pw_cc_library which implements the backend for test_threads_header. See
+# //pw_thread:thread_backend_test as an example.
+pw_cc_library(
+ name = "thread_facade_test",
+ srcs = [
+ "thread_facade_test.cc",
+ ],
+ deps = [
+ ":thread",
+ ":id",
+ ":test_threads_header",
+ "//pw_unit_test",
+ ],
+)
+
pw_cc_test(
name = "id_facade_test",
srcs = [
diff --git a/pw_thread/BUILD.gn b/pw_thread/BUILD.gn
index 436c1a1..b8a1dc1 100644
--- a/pw_thread/BUILD.gn
+++ b/pw_thread/BUILD.gn
@@ -42,6 +42,13 @@
sources = [ "sleep.cc" ]
}
+pw_facade("thread") {
+ backend = pw_thread_THREAD_BACKEND
+ public_configs = [ ":public_include_path" ]
+ public = [ "public/pw_thread/thread.h" ]
+ public_deps = [ ":id" ]
+}
+
pw_facade("yield") {
backend = pw_thread_YIELD_BACKEND
public_configs = [ ":public_include_path" ]
@@ -77,6 +84,28 @@
]
}
+if (pw_thread_THREAD_BACKEND != "") {
+ pw_source_set("test_threads") {
+ public_configs = [ ":public_include_path" ]
+ public = [ "public/pw_thread/test_threads.h" ]
+ public_deps = [ ":thread" ]
+ }
+
+ # To instantiate this facade test based on a selected backend to provide
+ # test_threads you can create a pw_test target which depends on this
+ # pw_source_set and a pw_source_set which provides the implementation of
+ # test_threads. See "$dir_pw_thread_stl:thread_backend_test" as an example.
+ pw_source_set("thread_facade_test") {
+ sources = [ "thread_facade_test.cc" ]
+ deps = [
+ ":id",
+ ":test_threads",
+ ":thread",
+ dir_pw_unit_test,
+ ]
+ }
+}
+
pw_test("yield_facade_test") {
enable_if = pw_thread_YIELD_BACKEND != "" && pw_thread_ID_BACKEND != ""
sources = [
diff --git a/pw_thread/backend.gni b/pw_thread/backend.gni
index 98a2738..6ecd378 100644
--- a/pw_thread/backend.gni
+++ b/pw_thread/backend.gni
@@ -19,6 +19,9 @@
# Backend for the pw_thread module's pw::thread::sleep_{for,until}.
pw_thread_SLEEP_BACKEND = ""
+ # Backend for the pw_thread module's pw::thread::Thread to create threads.
+ pw_thread_THREAD_BACKEND = ""
+
# Backend for the pw_thread module's pw::thread::yield.
pw_thread_YIELD_BACKEND = ""
diff --git a/pw_thread/public/pw_thread/test_threads.h b/pw_thread/public/pw_thread/test_threads.h
new file mode 100644
index 0000000..3b8d765
--- /dev/null
+++ b/pw_thread/public/pw_thread/test_threads.h
@@ -0,0 +1,24 @@
+// Copyright 2020 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
+
+#include "pw_thread/thread.h"
+
+namespace pw::thread::test {
+
+// Two test threads are used to verify the thread facade.
+const Options& TestOptionsThread0();
+const Options& TestOptionsThread1();
+
+} // namespace pw::thread::test
diff --git a/pw_thread/public/pw_thread/thread.h b/pw_thread/public/pw_thread/thread.h
new file mode 100644
index 0000000..d4632d5
--- /dev/null
+++ b/pw_thread/public/pw_thread/thread.h
@@ -0,0 +1,183 @@
+// Copyright 2020 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
+
+#include "pw_thread/id.h"
+
+// clang-format off
+// The backend's thread_native header must provide PW_THREAD_JOINING_ENABLED.
+#include "pw_thread_backend/thread_native.h"
+// clang-format on
+
+namespace pw::thread {
+
+// The Options contains the parameters needed for a thread to start.
+//
+// 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.
+class Options {
+ protected:
+ constexpr Options() = default;
+};
+
+// The class Thread represents a single thread of execution. Threads allow
+// multiple functions to execute concurrently.
+//
+// Threads begin execution immediately upon construction of the associated
+// thread object (pending any OS scheduling delays), starting at the top-level
+// function provided as a constructor argument. The return value of the
+// top-level function is ignored. The top-level function may communicate its
+// return value by modifying shared variables (which may require
+// synchronization, see pw_sync and std::atomic)
+//
+// 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.
+class Thread {
+ public:
+ using native_handle_type = backend::NativeThreadHandle;
+
+ // Creates a new thread object which does not represent a thread of execution
+ // yet.
+ Thread();
+
+ // Creates a new thread object which represents a thread of execution.
+ //
+ // Thread functions are permitted to return and must have the following
+ // 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
+ // 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.
+ using ThreadRoutine = void (*)(void* arg);
+ Thread(const Options& options, ThreadRoutine entry, void* arg = nullptr);
+
+ // Postcondition: The other thread no longer represents a thread of execution.
+ Thread& operator=(Thread&& other);
+
+ // Precondition: The thread must have been EITHER detached or joined.
+ ~Thread();
+
+ Thread(const Thread&) = delete;
+ Thread(Thread&&) = delete;
+ Thread& operator=(const Thread&) = delete;
+
+ // Returns a value of Thread::id identifying the thread associated with *this.
+ // If there is no thread associated, default constructed Thread::id is
+ // returned.
+ Id get_id() const;
+
+ // Checks if the Thread object identifies an active thread of execution which
+ // has not yet been detached. Specifically, returns true if get_id() !=
+ // pw::Thread::id() && detach() has NOT been invoked. So a default
+ // constructed thread is not joinable and neither is one which was detached.
+ //
+ // A thread that has not started or has finished executing code which was
+ // never detached, but has not yet been joined is still considered an active
+ // thread of execution and is therefore joinable.
+ bool joinable() const { return get_id() != Id(); }
+
+#if PW_THREAD_JOINING_ENABLED
+ // Blocks the current thread until the thread identified by *this finishes its
+ // execution.
+ //
+ // The completion of the thread identified by *this synchronizes with the
+ // corresponding successful return from join().
+ //
+ // No synchronization is performed on *this itself. Concurrently calling
+ // join() on the same thread object from multiple threads constitutes a data
+ // race that results in undefined behavior.
+ //
+ // Precondition: The thread must have been NEITHER detached nor joined.
+ //
+ // Postcondition: After calling detach *this no longer owns any thread.
+ void join();
+#endif // PW_THREAD_JOINING_ENABLED
+
+ // Separates the thread of execution from the thread object, allowing
+ // execution to continue independently. Any allocated resources will be freed
+ // once the thread exits.
+ //
+ // Precondition: The thread must have been NEITHER detached nor joined.
+ //
+ // Postcondition: After calling detach *this no longer owns any thread.
+ void detach();
+
+ // Exchanges the underlying handles of two thread objects.
+ void swap(Thread& other);
+
+ native_handle_type native_handle();
+
+ private:
+ // Note that just like std::thread, this is effectively just a pointer or
+ // reference to the native thread -- this does not contain any memory needed
+ // for the thread to execute.
+ //
+ // This may contain more than the native thread handle to enable functionality
+ // which is not always available such as joining, which may require a
+ // reference to a binary semaphore, or passing arguments to the thread's
+ // function.
+ backend::NativeThread native_type_;
+};
+
+} // namespace pw::thread
+
+#include "pw_thread_backend/thread_inline.h"
diff --git a/pw_thread/thread_facade_test.cc b/pw_thread/thread_facade_test.cc
new file mode 100644
index 0000000..510fe03
--- /dev/null
+++ b/pw_thread/thread_facade_test.cc
@@ -0,0 +1,144 @@
+// Copyright 2020 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 <atomic>
+
+#include "gtest/gtest.h"
+#include "pw_thread/id.h"
+#include "pw_thread/test_threads.h"
+#include "pw_thread/thread.h"
+
+using pw::thread::test::TestOptionsThread0;
+using pw::thread::test::TestOptionsThread1;
+
+namespace pw::thread {
+namespace {
+
+TEST(Thread, DefaultIds) {
+ Thread not_executing_thread;
+ EXPECT_EQ(not_executing_thread.get_id(), Id());
+}
+
+static void SetBoolTrue(void* arg) {
+ *reinterpret_cast<std::atomic<bool>*>(arg) = true;
+}
+
+// TODO(ewout): this currently doesn't work on backends with dynamic context
+// allocation disabled. Perhaps we could require the backend to provide
+// thread options for the facade unit tests to use?
+
+#if PW_THREAD_JOINING_ENABLED
+TEST(Thread, Join) {
+ Thread thread;
+ EXPECT_FALSE(thread.joinable());
+ std::atomic<bool> thread_ran = false;
+ thread = Thread(TestOptionsThread0(), SetBoolTrue, &thread_ran);
+ EXPECT_TRUE(thread.joinable());
+ thread.join();
+ EXPECT_EQ(thread.get_id(), Id());
+ EXPECT_TRUE(thread_ran);
+}
+#endif // PW_THREAD_JOINING_ENABLED
+
+TEST(Thread, Detach) {
+ Thread thread;
+ std::atomic<bool> thread_ran = false;
+ thread = Thread(TestOptionsThread0(), SetBoolTrue, &thread_ran);
+ EXPECT_NE(thread.get_id(), Id());
+#if PW_THREAD_JOINING_ENABLED
+ EXPECT_TRUE(thread.joinable());
+#endif // PW_THREAD_JOINING_ENABLED
+ thread.detach();
+ EXPECT_EQ(thread.get_id(), Id());
+#if PW_THREAD_JOINING_ENABLED
+ EXPECT_FALSE(thread.joinable());
+#endif // PW_THREAD_JOINING_ENABLED
+ // We could use a synchronization primitive here to wait until the thread
+ // finishes running to check that thread_ran is true, but that's covered by
+ // pw_sync instead. Instead we use an idiotic busy loop.
+ // - Assume our clock is < 6Ghz
+ // - Assume we can check the clock in a single cycle
+ // - Wait for up to 1/10th of a second @ 6Ghz, this may be a long period on a
+ // slower (i.e. real) machine.
+ constexpr uint64_t kMaxIterations = 6'000'000'000 / 10;
+ for (uint64_t i = 0; i < kMaxIterations; ++i) {
+ if (thread_ran)
+ break;
+ }
+ EXPECT_TRUE(thread_ran);
+}
+
+TEST(Thread, SwapWithoutExecution) {
+ Thread thread_0;
+ Thread thread_1;
+
+ // Make sure we can swap threads which are not associated with any execution.
+ thread_0.swap(thread_1);
+}
+
+TEST(Thread, SwapWithOneExecuting) {
+ Thread thread_0;
+ EXPECT_EQ(thread_0.get_id(), Id());
+
+ static std::atomic<bool> thread_ran = false;
+ Thread thread_1(TestOptionsThread1(), SetBoolTrue, &thread_ran);
+ EXPECT_NE(thread_1.get_id(), Id());
+
+ thread_0.swap(thread_1);
+ EXPECT_NE(thread_0.get_id(), Id());
+ EXPECT_EQ(thread_1.get_id(), Id());
+
+ thread_0.detach();
+ EXPECT_EQ(thread_0.get_id(), Id());
+}
+
+TEST(Thread, SwapWithTwoExecuting) {
+ static std::atomic<bool> thread_a_ran = false;
+ Thread thread_0(TestOptionsThread0(), SetBoolTrue, &thread_a_ran);
+ static std::atomic<bool> thread_b_ran = false;
+ Thread thread_1(TestOptionsThread1(), SetBoolTrue, &thread_b_ran);
+ const Id thread_a_id = thread_0.get_id();
+ EXPECT_NE(thread_a_id, Id());
+ const Id thread_b_id = thread_1.get_id();
+ EXPECT_NE(thread_b_id, Id());
+ EXPECT_NE(thread_a_id, thread_b_id);
+
+ thread_0.swap(thread_1);
+ EXPECT_EQ(thread_1.get_id(), thread_a_id);
+ EXPECT_EQ(thread_0.get_id(), thread_b_id);
+
+ thread_0.detach();
+ EXPECT_EQ(thread_0.get_id(), Id());
+ thread_1.detach();
+ EXPECT_EQ(thread_1.get_id(), Id());
+}
+
+TEST(Thread, MoveOperator) {
+ Thread thread_0;
+ EXPECT_EQ(thread_0.get_id(), Id());
+
+ std::atomic<bool> thread_ran = false;
+ Thread thread_1(TestOptionsThread1(), SetBoolTrue, &thread_ran);
+ EXPECT_NE(thread_1.get_id(), Id());
+
+ thread_0 = std::move(thread_1);
+ EXPECT_NE(thread_0.get_id(), Id());
+ EXPECT_EQ(thread_1.get_id(), Id());
+
+ thread_0.detach();
+ EXPECT_EQ(thread_0.get_id(), Id());
+}
+
+} // namespace
+} // namespace pw::thread
diff --git a/pw_thread_stl/BUILD b/pw_thread_stl/BUILD
index 6936cb3..e7624a6 100644
--- a/pw_thread_stl/BUILD
+++ b/pw_thread_stl/BUILD
@@ -15,6 +15,7 @@
load(
"//pw_build:pigweed.bzl",
"pw_cc_library",
+ "pw_cc_test",
)
package(default_visibility = ["//visibility:public"])
@@ -68,6 +69,48 @@
)
pw_cc_library(
+ name = "thread_headers",
+ hdrs = [
+ "public/pw_thread_stl/options.h",
+ "public/pw_thread_stl/thread_inline.h",
+ "public/pw_thread_stl/thread_native.h",
+ "public_overrides/pw_thread_backend/thread_inline.h",
+ "public_overrides/pw_thread_backend/thread_native.h",
+ ],
+ includes = [
+ "public",
+ "public_overrides",
+ ],
+)
+
+pw_cc_library(
+ name = "thread",
+ deps = [
+ ":thread_headers",
+ "//pw_thread:thread_facade",
+ ],
+)
+
+pw_cc_library(
+ name = "test_threads",
+ deps = [
+ "//pw_thread:thread_facade",
+ "//pw_thread:test_threads_header",
+ ],
+ srcs = [
+ "test_threads.cc",
+ ]
+)
+
+pw_cc_test(
+ name = "thread_backend_test",
+ deps = [
+ "//pw_thread:thread_facade_test",
+ ":test_threads",
+ ]
+)
+
+pw_cc_library(
name = "yield_headers",
hdrs = [
"public/pw_thread_stl/yield_inline.h",
diff --git a/pw_thread_stl/BUILD.gn b/pw_thread_stl/BUILD.gn
index f13723e..37ccf9b 100644
--- a/pw_thread_stl/BUILD.gn
+++ b/pw_thread_stl/BUILD.gn
@@ -17,6 +17,8 @@
import("$dir_pw_build/target_types.gni")
import("$dir_pw_chrono/backend.gni")
import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_thread/backend.gni")
+import("$dir_pw_unit_test/test.gni")
config("public_include_path") {
include_dirs = [ "public" ]
@@ -43,6 +45,24 @@
deps = [ "$dir_pw_thread:id.facade" ]
}
+# This target provides the backend for pw::thread::Thread with joining
+# joining capability.
+pw_source_set("thread") {
+ public_configs = [
+ ":public_include_path",
+ ":backend_config",
+ ]
+ public = [
+ "public/pw_thread_stl/options.h",
+ "public/pw_thread_stl/thread_inline.h",
+ "public/pw_thread_stl/thread_native.h",
+ "public_overrides/pw_thread_backend/thread_inline.h",
+ "public_overrides/pw_thread_backend/thread_native.h",
+ ]
+ allow_circular_includes_from = [ "$dir_pw_thread:thread.facade" ]
+ deps = [ "$dir_pw_thread:thread.facade" ]
+}
+
# This target provides the backend for pw::this_thread::sleep_{for,until}.
pw_source_set("sleep") {
public_configs = [
@@ -79,6 +99,24 @@
deps = [ "$dir_pw_thread:yield.facade" ]
}
+pw_test_group("tests") {
+ tests = [ ":thread_backend_test" ]
+}
+
+pw_source_set("test_threads") {
+ public_deps = [ "$dir_pw_thread:test_threads" ]
+ sources = [ "test_threads.cc" ]
+ deps = [ "$dir_pw_thread:thread" ]
+}
+
+pw_test("thread_backend_test") {
+ enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_stl:thread"
+ deps = [
+ ":test_threads",
+ "$dir_pw_thread:thread_facade_test",
+ ]
+}
+
pw_doc_group("docs") {
sources = [ "docs.rst" ]
}
diff --git a/pw_thread_stl/public/pw_thread_stl/options.h b/pw_thread_stl/public/pw_thread_stl/options.h
new file mode 100644
index 0000000..26f5bf5
--- /dev/null
+++ b/pw_thread_stl/public/pw_thread_stl/options.h
@@ -0,0 +1,26 @@
+// Copyright 2020 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
+
+#include "pw_thread/thread.h"
+
+namespace pw::thread::stl {
+
+// Unfortunately std::thread:attributes was not accepted into the C++ standard.
+// Instead, users are expected to start the thread and after dynamically adjust
+// the thread's attributes using std::thread::native_handle based on the native
+// threading APIs.
+class Options : public thread::Options {};
+
+} // namespace pw::thread::stl
diff --git a/pw_thread_stl/public/pw_thread_stl/thread_inline.h b/pw_thread_stl/public/pw_thread_stl/thread_inline.h
new file mode 100644
index 0000000..018e807
--- /dev/null
+++ b/pw_thread_stl/public/pw_thread_stl/thread_inline.h
@@ -0,0 +1,47 @@
+// Copyright 2020 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
+
+#include <thread>
+
+namespace pw::thread {
+
+inline Thread::Thread() : native_type_() {}
+
+inline Thread::Thread(const Options&, ThreadRoutine entry, void* arg) {
+ native_type_ = std::thread(entry, arg);
+}
+
+inline Thread& Thread::operator=(Thread&& other) {
+ native_type_ = std::move(other.native_type_);
+ return *this;
+}
+
+inline Thread::~Thread() = default;
+
+inline Id Thread::get_id() const { return native_type_.get_id(); }
+
+inline void Thread::join() { native_type_.join(); }
+
+inline void Thread::detach() { native_type_.detach(); }
+
+inline void Thread::swap(Thread& other) {
+ native_type_.swap(other.native_handle());
+}
+
+inline Thread::native_handle_type Thread::native_handle() {
+ return native_type_;
+}
+
+} // namespace pw::thread
diff --git a/pw_thread_stl/public/pw_thread_stl/thread_native.h b/pw_thread_stl/public/pw_thread_stl/thread_native.h
new file mode 100644
index 0000000..d3592de
--- /dev/null
+++ b/pw_thread_stl/public/pw_thread_stl/thread_native.h
@@ -0,0 +1,25 @@
+// Copyright 2020 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
+
+#include <thread>
+
+#define PW_THREAD_JOINING_ENABLED 1
+
+namespace pw::thread::backend {
+
+using NativeThread = std::thread;
+using NativeThreadHandle = std::thread&;
+
+} // namespace pw::thread::backend
diff --git a/pw_thread_stl/public_overrides/pw_thread_backend/thread_inline.h b/pw_thread_stl/public_overrides/pw_thread_backend/thread_inline.h
new file mode 100644
index 0000000..2a52ee8
--- /dev/null
+++ b/pw_thread_stl/public_overrides/pw_thread_backend/thread_inline.h
@@ -0,0 +1,16 @@
+// Copyright 2020 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
+
+#include "pw_thread_stl/thread_inline.h"
diff --git a/pw_thread_stl/public_overrides/pw_thread_backend/thread_native.h b/pw_thread_stl/public_overrides/pw_thread_backend/thread_native.h
new file mode 100644
index 0000000..272a595
--- /dev/null
+++ b/pw_thread_stl/public_overrides/pw_thread_backend/thread_native.h
@@ -0,0 +1,16 @@
+// Copyright 2020 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
+
+#include "pw_thread_stl/thread_native.h"
diff --git a/pw_thread_stl/test_threads.cc b/pw_thread_stl/test_threads.cc
new file mode 100644
index 0000000..e9e35bf
--- /dev/null
+++ b/pw_thread_stl/test_threads.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 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_stl/options.h"
+
+namespace pw::thread::test {
+
+// The STL doesn't offer any options so the default constructed options are used
+// directly.
+
+const Options& TestOptionsThread0() {
+ static constexpr stl::Options thread_0_options;
+ return thread_0_options;
+}
+
+const Options& TestOptionsThread1() {
+ static constexpr stl::Options thread_1_options;
+ return thread_1_options;
+}
+
+} // namespace pw::thread::test
diff --git a/targets/host/target_toolchains.gni b/targets/host/target_toolchains.gni
index fc205f8..aa64fa5 100644
--- a/targets/host/target_toolchains.gni
+++ b/targets/host/target_toolchains.gni
@@ -90,6 +90,7 @@
pw_thread_ID_BACKEND = "$dir_pw_thread_stl:id"
pw_thread_SLEEP_BACKEND = "$dir_pw_thread_stl:sleep"
pw_thread_YIELD_BACKEND = "$dir_pw_thread_stl:yield"
+ pw_thread_THREAD_BACKEND = "$dir_pw_thread_stl:thread"
}
_os_specific_config = {