1 // Copyright 2021 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include "pw_thread/id.h"
17 #include "pw_thread/thread_core.h"
18 
19 // clang-format off
20 // The backend's thread_native header must provide PW_THREAD_JOINING_ENABLED.
21 #include "pw_thread_backend/thread_native.h"
22 // clang-format on
23 
24 namespace pw::thread {
25 
26 // The Options contains the parameters needed for a thread to start.
27 //
28 // Options are backend specific and ergo the generic base class cannot be
29 // directly instantiated.
30 //
31 // The attributes which can be set through the options are backend specific
32 // but may contain things like the thread name, priority, scheduling policy,
33 // core/processor affinity, and/or an optional reference to a pre-allocated
34 // Context (the collection of memory allocations needed for a thread to run).
35 //
36 // Options shall NOT permit starting as detached, this must be done explicitly
37 // through the Thread API.
38 //
39 // Options must not contain any memory needed for a thread to run (TCB,
40 // stack, etc.). The Options may be deleted or re-used immediately after
41 // starting a thread.
42 class Options {
43  protected:
44   constexpr Options() = default;
45 };
46 
47 // The class Thread can represent a single thread of execution. Threads allow
48 // multiple functions to execute concurrently.
49 //
50 // Threads may begin execution immediately upon construction of the associated
51 // thread object (pending any OS scheduling delays), starting at the top-level
52 // function provided as a constructor argument. The return value of the
53 // top-level function is ignored. The top-level function may communicate its
54 // return value by modifying shared variables (which may require
55 // synchronization, see pw_sync and std::atomic)
56 //
57 // Thread objects may also be in the state that does not represent any thread
58 // (after default construction, move from, detach, or join), and a thread of
59 // execution may be not associated with any thread objects (after detach).
60 //
61 // No two Thread objects may represent the same thread of execution; Thread is
62 // not CopyConstructible or CopyAssignable, although it is MoveConstructible and
63 // MoveAssignable.
64 class Thread {
65  public:
66   using native_handle_type = backend::NativeThreadHandle;
67 
68   // Creates a new thread object which does not represent a thread of execution
69   // yet.
70   Thread();
71 
72   // Creates a new thread object which represents a thread of execution.
73   //
74   // Thread functions are permitted to return and must have the following
75   // ThreadRoutine signature:
76   //   void example_function(void *arg);
77   //
78   // To invoke a member method of a class a static lambda closure can be used
79   // to ensure the dispatching closure is not destructed before the thread is
80   // done executing. For example:
81   //   class Foo {
82   //    public:
83   //     void DoBar() {}
84   //   };
85   //   Foo foo;
86   //
87   //   static auto invoke_foo_do_bar = [](void *void_foo_ptr) {
88   //       //  If needed, additional arguments could be set here.
89   //       static_cast<Foo*>(void_foo_ptr)->DoBar();
90   //   };
91   //
92   //   // Now use the lambda closure as the thread entry, passing the foo's
93   //   // this as the argument.
94   //   Thread thread(options, invoke_foo_do_bar, &foo);
95   //   thread.detach();
96   //
97   // Alternatively a helper ThreadCore interface can be implemented by an object
98   // so that a static lambda closure or function is not needed to dispatch to
99   // a member function without arguments. For example:
100   //   class Foo : public ThreadCore {
101   //    private:
102   //     void Run() override {}
103   //   };
104   //   Foo foo;
105   //
106   //   // Now create the thread, using foo directly.
107   //   Thread(options, foo).detach();
108   //
109   // Postcondition: The thread get EITHER detached or joined.
110   //
111   // NOTE: Options have a default constructor, however default options are not
112   // portable! Default options can only work if threads are dynamically
113   // allocated by default, meaning default options cannot work on backends which
114   // require static thread allocations. In addition on some schedulers
115   // default options may not work for other reasons.
116   using ThreadRoutine = void (*)(void* arg);
117   Thread(const Options& options, ThreadRoutine entry, void* arg = nullptr);
118   Thread(const Options& options, ThreadCore& thread_core);
119 
120   // Postcondition: The other thread no longer represents a thread of execution.
121   Thread& operator=(Thread&& other);
122 
123   // Precondition: The thread must have been EITHER detached or joined.
124   ~Thread();
125 
126   Thread(const Thread&) = delete;
127   Thread(Thread&&) = delete;
128   Thread& operator=(const Thread&) = delete;
129 
130   // Returns a value of Thread::id identifying the thread associated with *this.
131   // If there is no thread associated, default constructed Thread::id is
132   // returned.
133   Id get_id() const;
134 
135   // Checks if the Thread object identifies an active thread of execution which
136   // has not yet been detached. Specifically, returns true if get_id() !=
137   // pw::Thread::id() && detach() has NOT been invoked. So a default
138   // constructed thread is not joinable and neither is one which was detached.
139   //
140   // A thread that has not started or has finished executing code which was
141   // never detached, but has not yet been joined is still considered an active
142   // thread of execution and is therefore joinable.
joinable()143   bool joinable() const { return get_id() != Id(); }
144 
145 #if PW_THREAD_JOINING_ENABLED
146   // Blocks the current thread until the thread identified by *this finishes its
147   // execution.
148   //
149   // The completion of the thread identified by *this synchronizes with the
150   // corresponding successful return from join().
151   //
152   // No synchronization is performed on *this itself. Concurrently calling
153   // join() on the same thread object from multiple threads constitutes a data
154   // race that results in undefined behavior.
155   //
156   // Precondition: The thread must have been NEITHER detached nor joined.
157   //
158   // Postcondition: After calling detach *this no longer owns any thread.
159   void join();
160 #endif  // PW_THREAD_JOINING_ENABLED
161 
162   // Separates the thread of execution from the thread object, allowing
163   // execution to continue independently. Any allocated resources will be freed
164   // once the thread exits.
165   //
166   // Precondition: The thread must have been NEITHER detached nor joined.
167   //
168   // Postcondition: After calling detach *this no longer owns any thread.
169   void detach();
170 
171   // Exchanges the underlying handles of two thread objects.
172   void swap(Thread& other);
173 
174   native_handle_type native_handle();
175 
176  private:
177   // Note that just like std::thread, this is effectively just a pointer or
178   // reference to the native thread -- this does not contain any memory needed
179   // for the thread to execute.
180   //
181   // This may contain more than the native thread handle to enable functionality
182   // which is not always available such as joining, which may require a
183   // reference to a binary semaphore, or passing arguments to the thread's
184   // function.
185   backend::NativeThread native_type_;
186 };
187 
188 }  // namespace pw::thread
189 
190 #include "pw_thread_backend/thread_inline.h"
191