1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#pragma once
4
5#include "Prerequisites/BsPrerequisitesUtil.h"
6#include "Utility/BsModule.h"
7
8namespace bs
9{
10 /** @addtogroup Threading
11 * @{
12 */
13
14 class ThreadPool;
15
16 /** Handle to a thread managed by ThreadPool. */
17 class BS_UTILITY_EXPORT HThread
18 {
19 public:
20 HThread() = default;;
21 HThread(ThreadPool* pool, UINT32 threadId);
22
23 /** Block the calling thread until the thread this handle points to completes. */
24 void blockUntilComplete();
25
26 private:
27 UINT32 mThreadId = 0;
28 ThreadPool* mPool = nullptr;
29 };
30
31 /** @} */
32 /** @addtogroup Internal-Utility
33 * @{
34 */
35
36 /** @addtogroup Threading-Internal
37 * @{
38 */
39
40 /** Wrapper around a thread that is used within ThreadPool. */
41 class BS_UTILITY_EXPORT PooledThread
42 {
43 public:
44 PooledThread(const String& name)
45 :mName(name)
46 { }
47
48 virtual ~PooledThread() = default;
49
50 /** Initializes the pooled thread. Must be called right after the object is constructed. */
51 void initialize();
52
53 /**
54 * Starts executing the given worker method.
55 *
56 * @note
57 * Caller must ensure worker method is not null and that the thread is currently idle, otherwise undefined behavior
58 * will occur.
59 */
60 void start(std::function<void()> workerMethod, UINT32 id);
61
62 /**
63 * Attempts to join the currently running thread and destroys it. Caller must ensure that any worker method
64 * currently running properly returns, otherwise this will block indefinitely.
65 */
66 void destroy();
67
68 /** Returns true if the thread is idle and new worker method can be scheduled on it. */
69 bool isIdle();
70
71 /** Returns how long has the thread been idle. Value is undefined if thread is not idle. */
72 time_t idleTime();
73
74 /** Sets a name of the thread. */
75 void setName(const String& name);
76
77 /** Gets unique ID of the currently executing thread. */
78 UINT32 getId() const;
79
80 /** Blocks the current thread until this thread completes. Returns immediately if the thread is idle. */
81 void blockUntilComplete();
82
83 /** Called when the thread is first created. */
84 virtual void onThreadStarted(const String& name) = 0;
85
86 /** Called when the thread is being shut down. */
87 virtual void onThreadEnded(const String& name) = 0;
88
89 protected:
90 friend class HThread;
91
92 /** Primary worker method that is ran when the thread is first initialized. */
93 void run();
94
95 protected:
96 std::function<void()> mWorkerMethod;
97 String mName;
98 UINT32 mId = 0;
99 bool mIdle = true;
100 bool mThreadStarted = false;
101 bool mThreadReady = false;
102
103 time_t mIdleTime = 0;
104
105 Thread* mThread;
106 mutable Mutex mMutex;
107 Signal mStartedCond;
108 Signal mReadyCond;
109 Signal mWorkerEndedCond;
110 };
111
112 /**
113 * @copydoc PooledThread
114 *
115 * @tparam ThreadPolicy Allows you specify a policy with methods that will get called whenever a new thread is created
116 * or when a thread is destroyed.
117 */
118 template<class ThreadPolicy>
119 class TPooledThread : public PooledThread
120 {
121 public:
122 using PooledThread::PooledThread;
123
124 /** @copydoc PooledThread::onThreadStarted */
125 void onThreadStarted(const String& name) override
126 {
127 ThreadPolicy::onThreadStarted(name);
128 }
129
130 /** @copydoc PooledThread::onThreadEnded */
131 void onThreadEnded(const String& name) override
132 {
133 ThreadPolicy::onThreadEnded(name);
134 }
135 };
136
137 /** @} */
138 /** @} */
139
140 /** @addtogroup Threading
141 * @{
142 */
143
144 /**
145 * Class that maintains a pool of threads we can easily retrieve and use for any task. This saves on the cost of
146 * creating and destroying threads.
147 */
148 class BS_UTILITY_EXPORT ThreadPool : public Module<ThreadPool>
149 {
150 public:
151 /**
152 * Constructs a new thread pool
153 *
154 * @param[in] threadCapacity Default thread capacity, the pool will always try to keep this many threads available.
155 * @param[in] maxCapacity (optional) Maximum number of threads the pool can create. If we go over this limit an
156 * exception will be thrown.
157 * @param[in] idleTimeout (optional) How many seconds do threads need to be idle before we remove them from the pool.
158 */
159 ThreadPool(UINT32 threadCapacity, UINT32 maxCapacity = 16, UINT32 idleTimeout = 60);
160 virtual ~ThreadPool();
161
162 /**
163 * Find an unused thread (or creates a new one) and runs the specified worker method on it.
164 *
165 * @param[in] name A name you may use for more easily identifying the thread.
166 * @param[in] workerMethod The worker method to be called by the thread.
167 * @return A thread handle you may use for monitoring the thread execution.
168 */
169 HThread run(const String& name, std::function<void()> workerMethod);
170
171 /**
172 * Stops all threads and destroys them. Caller must ensure each threads worker method returns otherwise this will
173 * never return.
174 */
175 void stopAll();
176
177 /** Clear any unused threads that are over the capacity. */
178 void clearUnused();
179
180 /** Returns the number of unused threads in the pool. */
181 UINT32 getNumAvailable() const;
182
183 /** Returns the number of running threads in the pool. */
184 UINT32 getNumActive() const;
185
186 /** Returns the total number of created threads in the pool (both running and unused). */
187 UINT32 getNumAllocated() const;
188
189 protected:
190 friend class HThread;
191
192 Vector<PooledThread*> mThreads;
193
194 /** Creates a new thread to be used by the pool. */
195 virtual PooledThread* createThread(const String& name) = 0;
196
197 /** Destroys the specified thread. Caller needs to make sure the thread is actually shut down beforehand. */
198 void destroyThread(PooledThread* thread);
199
200 /**
201 * Returns the first unused thread if one exists, otherwise creates a new one.
202 *
203 * @param[in] name Name to assign the thread.
204 *
205 * @note Throws an exception if we have reached our maximum thread capacity.
206 */
207 PooledThread* getThread(const String& name);
208
209 UINT32 mDefaultCapacity;
210 UINT32 mMaxCapacity;
211 UINT32 mIdleTimeout;
212 /** unused check counter */
213 UINT32 mAge = 0;
214
215 std::atomic_uint mUniqueId;
216 mutable Mutex mMutex;
217 };
218
219 /** @} */
220 /** @addtogroup Internal-Utility
221 * @{
222 */
223
224 /** @addtogroup Threading-Internal
225 * @{
226 */
227
228 /** Policy used for thread start & end used by the ThreadPool. */
229 class ThreadNoPolicy
230 {
231 public:
232 static void onThreadStarted(const String& name) { }
233 static void onThreadEnded(const String& name) { }
234 };
235
236 /**
237 * @copydoc ThreadPool
238 *
239 * @tparam ThreadPolicy Allows you specify a policy with methods that will get called whenever a new thread is created
240 * or when a thread is destroyed.
241 */
242 template<class ThreadPolicy = ThreadNoPolicy>
243 class TThreadPool : public ThreadPool
244 {
245 public:
246 TThreadPool(UINT32 threadCapacity, UINT32 maxCapacity = 16, UINT32 idleTimeout = 60)
247 :ThreadPool(threadCapacity, maxCapacity, idleTimeout)
248 {
249
250 }
251
252 protected:
253 /** @copydoc ThreadPool::createThread */
254 PooledThread* createThread(const String& name) override
255 {
256 PooledThread* newThread = bs_new<TPooledThread<ThreadPolicy>>(name);
257 newThread->initialize();
258
259 return newThread;
260 }
261 };
262
263 /** @} */
264 /** @} */
265}
266