// 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: constexpr Options() = default; }; // 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(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(); #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"