blob: 9f36ba207376c91a5d78f084e4534aeb0e705de1 [file] [log] [blame]
// 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
#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.
#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:
// We can't use `= default` here, because it allows to create an Options
// instance in C++17 with `pw::thread::Options{}` syntax.
constexpr Options() {}
};
// The class Thread can represent a single thread of execution. Threads allow
// multiple functions to execute concurrently.
//
// 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 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
// 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(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. 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);
// 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();
#else
template <typename kUnusedType = void>
void join() {
static_assert(kJoiningEnabled<kUnusedType>,
"The selected pw_thread_THREAD backend does not have join() "
"enabled (AKA PW_THREAD_JOINING_ENABLED = 1)");
}
#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:
template <typename...>
static constexpr std::bool_constant<PW_THREAD_JOINING_ENABLED>
kJoiningEnabled = {};
// 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"