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 | |
8 | namespace 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 | |