Nektar++
Thread.h
Go to the documentation of this file.
1///////////////////////////////////////////////////////////////////////////////
2//
3// File: Thread.h
4//
5// For more information, please see: http://www.nektar.info
6//
7// The MIT License
8//
9// Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
10// Department of Aeronautics, Imperial College London (UK), and Scientific
11// Computing and Imaging Institute, University of Utah (USA).
12//
13// Permission is hereby granted, free of charge, to any person obtaining a
14// copy of this software and associated documentation files (the "Software"),
15// to deal in the Software without restriction, including without limitation
16// the rights to use, copy, modify, merge, publish, distribute, sublicense,
17// and/or sell copies of the Software, and to permit persons to whom the
18// Software is furnished to do so, subject to the following conditions:
19//
20// The above copyright notice and this permission notice shall be included
21// in all copies or substantial portions of the Software.
22//
23// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29// DEALINGS IN THE SOFTWARE.
30//
31// Description:
32//
33///////////////////////////////////////////////////////////////////////////////
34
35#ifndef NEKTAR_LIB_UTILITIES_THREAD_H_
36#define NEKTAR_LIB_UTILITIES_THREAD_H_
37
38#include <memory>
39#include <mutex>
40#include <queue>
41#include <shared_mutex>
42#include <thread>
43#include <vector>
44
46
47namespace Nektar::Thread
48{
49
50/**
51 * @brief Identifies the algorithm for scheduling.
52 *
53 * Currently there are two strategies to determine the number of jobs.
54 * e_static always loads m_chunkSize jobs onto each worker,
55 * e_guided uses a simple algorithm that loads a larger proportion of jobs
56 * onto the worker as the size of the queue list is large, but scales down
57 * to no fewer than m_chunkSize as the queue empties.
58 *
59 * @see ThreadManager::SetChunkSize()
60 *
61 */
63{
66};
67
68class ThreadManager;
69typedef std::shared_ptr<ThreadManager> ThreadManagerSharedPtr;
73
74/**
75 * @brief Base class for tasks to be sent to the ThreadManager to run.
76 *
77 * For a parallel region the overall task should be divided into tasklets, each
78 * capable of running independently of other tasklets, in any order. Each of
79 * these tasklets should be represented by an instance of a subclass of
80 * ThreadJob.
81 *
82 * Exactly how to partition the overall task will be problem dependent, but the
83 * ideal case is to have as many tasklets as there are worker threads (available
84 * through ThreadManager::GetNumWorkers() ) and for each tasklet to take the
85 * same amount of computational effort. Clearly, the tasklets should not
86 * overwrite one another's data, and data locations they are writing to should
87 * be sufficiently far apart to avoid cache ping pong.
88 *
89 * Subclasses may define as many variables and functions as are necessary.
90 * Instance of the subclass will be destructed once the ThreadManager has
91 * finished the Run() method.
92 */
94{
95public:
96 /// Base constructor
98 /// Base destructor.
100
101 /**
102 * This method will be called when the task is loaded
103 * onto a worker thread and is ready to run. When Run
104 * has finished this instance will be destructed.
105 */
106 LIB_UTILITIES_EXPORT virtual void Run() = 0;
107
108 /// Set number of worker threads.
109 LIB_UTILITIES_EXPORT void SetWorkerNum(unsigned int num);
110
111protected:
112 /**
113 * Returns an integer identifying the worker thread the
114 * job is running on. Value will be 0...N, where N is
115 * the number of active worker threads.
116 */
117 unsigned int GetWorkerNum();
118
119private:
120 unsigned int m_workerNum;
121};
122
123/**
124 * @brief The interface class for the controller for worker threads and jobs.
125 *
126 * There may be multiple instances of implementations of this class.
127 * They are instantiated by ThreadMaster::CreateInstance(string, int).
128 * Use ThreadMaster::GetInstance(string)
129 * to get a pointer to the instance. Each ThreadManager is associated
130 * in ThreadMaster with a string.
131 *
132 * Jobs are sent to the worker threads by placing them in the
133 * queue with QueueJob() and QueueJobs(). Jobs may be run in
134 * indeterminate order. Jobs are permitted to run *immediately*
135 * they are queued. If this is not desired set the number of active
136 * workers to 0 until all jobs are queued.
137 *
138 * Jobs are taken from the master queue by active worker threads.
139 *
140 * We have the concept of the *master thread*. This is the thread
141 * that instantiates the ThreadManager, calls QueueJob(), Wait()
142 * and so on. This thread must be included in the number of threads
143 * started when the ThreadManager is created, so that no more than
144 * this number of threads are ever executing work at any time. The
145 * master thread is assumed to be working unless it is in Wait().
146 *
147 * Thus if the ThreadManager is called with CreateInstance(string s, int N)
148 * there should be N threads /em including the master thread. If the master
149 * thread calls Wait() (which causes the master thread to sleep until
150 * the job queue is empty) then the master thread should become available
151 * to run jobs from the queue.
152 *
153 * Only the master thread should call ThreadManager methods. Although
154 * it should always be possible for code to identify whether it's the
155 * master thread (through design) there is a InThread() method that returns
156 * true if the thread is a worker.
157 *
158 */
159class ThreadManager : public std::enable_shared_from_this<ThreadManager>
160{
161public:
162 /// Destructor.
164 /**
165 * @brief Pass a list of tasklets to the master queue.
166 * @param joblist Vector of ThreadJob pointers.
167 *
168 * The list of jobs is copied into the master queue. Jobs may be
169 * available for running *immediately*, even before the list has been
170 * fully copied. This can have consequences for the scheduling. If
171 * this is an issue then suspend the workers with SetNumWorkers(0) until
172 * the jobs are queued.
173 *
174 * @see SchedType
175 */
176 LIB_UTILITIES_EXPORT void QueueJobs(std::vector<ThreadJob *> &joblist)
177 {
178 v_QueueJobs(joblist);
179 }
180 /**
181 * @brief Pass a single job to the master queue.
182 * @param job A pointer to a ThreadJob subclass.
183 *
184 * The job may become available for running immediately. If this is an
185 * issue then suspend the workers with SetNumWorkers(0) until the jobs
186 * are queued.
187 */
189 {
190 v_QueueJob(job);
191 }
192 /**
193 * @brief Return the number of active workers.
194 *
195 * Active workers are threads that are either running jobs
196 * or are waiting for jobs to be queued.
197 */
199 {
200 return v_GetNumWorkers();
201 }
202 /**
203 * @brief Returns the worker number of the executing thread.
204 *
205 * Returns an unsigned int between 0 and N-1 where N is the number of
206 * active worker threads. Repeated calls from within this thread will
207 * always return the same value and the value will be the same as
208 * returned from ThreadJob.GetWorkerNum(). The same thread will run a
209 * job until it finishes.
210 *
211 * Although if there are active threads then thread 0 is always one of
212 * them, it is possible that thread 0 does not run for a given set of
213 * jobs. For example, if there are 4 active threads and 3 jobs are
214 * submitted with a e_static scheduling strategy and a chunksize of 1,
215 * then it is possible that threads 1,2, and 3 pick up the jobs and
216 * thread 0 remains idle.
217 *
218 * Returns 0 if called by non-thread.
219 */
221 {
222 return v_GetWorkerNum();
223 }
224 /**
225 * @brief Sets the number of active workers.
226 * @param num The number of active workers.
227 *
228 * Active workers are threads that are either running jobs or are
229 * waiting for jobs to be queued.
230 *
231 * If num is greater than the maximum allowed number of active workers,
232 * then the maximum value will be used instead.
233 */
234 LIB_UTILITIES_EXPORT void SetNumWorkers(const unsigned int num)
235 {
236 v_SetNumWorkers(num);
237 }
238 /**
239 * @brief Sets the number of active workers to the maximum.
240 *
241 * Sets the number of active workers to the maximum available.
242 */
244 {
246 }
247 /**
248 * @brief Gets the maximum available number of threads.
249 * @return The maximum number of workers.
250 */
252 {
253 return v_GetMaxNumWorkers();
254 }
255 /**
256 * @brief Waits until all queued jobs are finished.
257 *
258 * If there are no jobs running or queued this method returns
259 * immediately. Otherwise it blocks until the queue is empty and the
260 * worker threads are idle.
261 *
262 * Implementations *must* ensure that trivial deadlocks are not possible
263 * from this method, that is, that this code:
264 * @code
265 * // assume ThreadManager* tm
266 * // assume SomeJob is subclass of ThreadJob
267 * // assume SomeJob job
268 *
269 * tm->SetNumWorkers(0);
270 * tm->QueueJob(job);
271 * tm->Wait();
272 * @endcode
273 * does not wait forever. Since the master thread is counted in the
274 * number of worker threads, implementations should increase the number
275 * of active workers by 1 on entering Wait().
276 */
278 {
279 v_Wait();
280 }
281 /**
282 * @brief Controls how many jobs are sent to each worker at a time.
283 *
284 * The exact meaning of this parameter depends on the current scheduling
285 * algorithm.
286 *
287 * @see SchedType
288 * @see SetSchedType()
289 */
290 LIB_UTILITIES_EXPORT void SetChunkSize(unsigned int chnk)
291 {
292 v_SetChunkSize(chnk);
293 }
294 /**
295 * @brief Sets the current scheduling algorithm.
296 * @see SetChunkSize()
297 */
299 {
301 }
302 /**
303 * @brief Indicates whether the code is in a worker thread or not.
304 * @return True if the caller is in a worker thread.
305 */
307 {
308 return v_InThread();
309 }
310 /**
311 * @brief A calling threads holds until all active threads call this
312 * method.
313 *
314 * When called, the calling thread will sleep until all active workers
315 * have called this method. Once all have done so all threads awake and
316 * continue execution.
317 *
318 * @note Behaviour is likely undefined if the number of active workers
319 * is altered after a thread has called this method. It is only safe to
320 * call SetNumWorkers() when no threads are holding.
321 */
323 {
324 v_Hold();
325 }
326 /**
327 * @brief Returns a description of the type of threading.
328 *
329 * E.g. "Threading with Boost"
330 */
331 LIB_UTILITIES_EXPORT const std::string &GetType()
332 {
333 return v_GetType();
334 }
335
336 /// ThreadManager implementation.
338 {
339 return v_IsInitialised();
340 }
341
342 inline int GetThrFromPartition(int pPartition)
343 {
344 return pPartition % GetMaxNumWorkers();
345 }
346
347 inline int GetRankFromPartition(int pPartition)
348 {
349 return pPartition / GetMaxNumWorkers();
350 }
351
352 inline int GetPartitionFromRankThr(int pRank, unsigned int pThr)
353 {
354 return pRank * GetMaxNumWorkers() + pThr;
355 }
356
357protected:
359 std::vector<ThreadJob *> &joblist) = 0;
361 LIB_UTILITIES_EXPORT virtual unsigned int v_GetNumWorkers() = 0;
362 LIB_UTILITIES_EXPORT virtual unsigned int v_GetWorkerNum() = 0;
364 const unsigned int num) = 0;
366 LIB_UTILITIES_EXPORT virtual unsigned int v_GetMaxNumWorkers() = 0;
367 LIB_UTILITIES_EXPORT virtual void v_Wait() = 0;
368 LIB_UTILITIES_EXPORT virtual void v_SetChunkSize(unsigned int chnk) = 0;
371 LIB_UTILITIES_EXPORT virtual void v_Hold() = 0;
372 LIB_UTILITIES_EXPORT virtual const std::string &v_GetType() const = 0;
374};
375
376typedef std::unique_lock<std::shared_mutex> WriteLock;
377typedef std::shared_lock<std::shared_mutex> ReadLock;
378
379/**
380 * A class to manage multiple ThreadManagers. It also acts as a cut-out during
381 * static initialisation, where code attempts to grab a ThreadManager before any
382 * can have been initialised. When this happens the ThreadMaster creates a
383 * simple implementation of ThreadManager, ThreadStartupManager, which behaves
384 * as a single-threaded ThreadManager that fails if anything non-trivial is
385 * attempted. This means code should be cautious about caching the value of
386 * GetInstance(string), as it might change once that particular threading system
387 * is initialised.
388 *
389 * Multiple ThreadManagers should now be possible. Each is identified with a
390 * string and can be recovered by calling
391 * Thread::GetThreadMaster().GetInstance(string).
392 *
393 * ThreadMaster is thread-safe apart from CreateInstance. Call CreateInstance
394 * in single threaded code only.
395 */
397{
398private:
399 std::vector<ThreadManagerSharedPtr> m_threadManagers;
400 std::shared_mutex m_mutex;
401 std::string m_threadingType;
402
403public:
405 {
408 };
409 /// Constructor
411 /// Destructor
413 /// Sets what ThreadManagers will be created in CreateInstance.
414 LIB_UTILITIES_EXPORT void SetThreadingType(const std::string &p_type);
415 /// Gets the ThreadManager associated with string s.
417 const ThreadManagerName t);
418 /// Creates an instance of a ThreadManager (which one is determined by
419 /// a previous call to SetThreadingType) and associates it with
420 /// the string s.
422 CreateInstance(const ThreadManagerName t, unsigned int nThr);
423};
425
426/**
427 * @brief A default ThreadManager.
428 *
429 * This will be returned by ThreadMaster if a ThreadManager has not been
430 * initialised, such as if the code is still in static initialisation.
431 *
432 * This manager pretends to be a ThreadManager with 1 thread. It will
433 * cause an error if anything more than trivial functions are called.
434 */
436{
437public:
440 ~ThreadStartupManager() override;
441
442protected:
443 void v_QueueJobs(std::vector<ThreadJob *> &joblist) override;
444 void v_QueueJob(ThreadJob *job) override;
445 unsigned int v_GetNumWorkers() override;
446 unsigned int v_GetWorkerNum() override;
447 void v_SetNumWorkers(const unsigned int num) override;
448 void v_SetNumWorkers() override;
449 unsigned int v_GetMaxNumWorkers() override;
450 void v_Wait() override;
451 void v_SetChunkSize(unsigned int chnk) override;
452 void v_SetSchedType(SchedType s) override;
453 bool v_InThread() override;
454 void v_Hold() override;
455 bool v_IsInitialised() override;
456 const std::string &v_GetType() const override;
457
458private:
459 // Do not allow assignment as m_type is const
461
462 const std::string m_type;
463};
464
465} // namespace Nektar::Thread
466#endif /* THREAD_H_ */
#define LIB_UTILITIES_EXPORT
Provides a generic Factory class.
Base class for tasks to be sent to the ThreadManager to run.
Definition: Thread.h:94
virtual ~ThreadJob()
Base destructor.
Definition: Thread.cpp:62
void SetWorkerNum(unsigned int num)
Set number of worker threads.
Definition: Thread.cpp:72
virtual void Run()=0
ThreadJob()
Base constructor.
Definition: Thread.cpp:54
unsigned int m_workerNum
Definition: Thread.h:120
unsigned int GetWorkerNum()
Definition: Thread.cpp:80
The interface class for the controller for worker threads and jobs.
Definition: Thread.h:160
virtual unsigned int v_GetWorkerNum()=0
virtual void v_QueueJobs(std::vector< ThreadJob * > &joblist)=0
unsigned int GetNumWorkers()
Return the number of active workers.
Definition: Thread.h:198
void QueueJobs(std::vector< ThreadJob * > &joblist)
Pass a list of tasklets to the master queue.
Definition: Thread.h:176
bool InThread()
Indicates whether the code is in a worker thread or not.
Definition: Thread.h:306
int GetPartitionFromRankThr(int pRank, unsigned int pThr)
Definition: Thread.h:352
int GetThrFromPartition(int pPartition)
Definition: Thread.h:342
virtual void v_SetSchedType(SchedType s)=0
virtual ~ThreadManager()
Destructor.
Definition: Thread.cpp:102
void SetNumWorkers(const unsigned int num)
Sets the number of active workers.
Definition: Thread.h:234
void Wait()
Waits until all queued jobs are finished.
Definition: Thread.h:277
virtual unsigned int v_GetNumWorkers()=0
void SetSchedType(SchedType s)
Sets the current scheduling algorithm.
Definition: Thread.h:298
void QueueJob(ThreadJob *job)
Pass a single job to the master queue.
Definition: Thread.h:188
virtual void v_QueueJob(ThreadJob *job)=0
virtual void v_SetChunkSize(unsigned int chnk)=0
virtual void v_SetNumWorkers()=0
virtual unsigned int v_GetMaxNumWorkers()=0
const std::string & GetType()
Returns a description of the type of threading.
Definition: Thread.h:331
int GetRankFromPartition(int pPartition)
Definition: Thread.h:347
unsigned int GetMaxNumWorkers()
Gets the maximum available number of threads.
Definition: Thread.h:251
virtual bool v_IsInitialised()
Definition: Thread.cpp:91
void Hold()
A calling threads holds until all active threads call this method.
Definition: Thread.h:322
unsigned int GetWorkerNum()
Returns the worker number of the executing thread.
Definition: Thread.h:220
bool IsInitialised()
ThreadManager implementation.
Definition: Thread.h:337
void SetNumWorkers()
Sets the number of active workers to the maximum.
Definition: Thread.h:243
virtual const std::string & v_GetType() const =0
void SetChunkSize(unsigned int chnk)
Controls how many jobs are sent to each worker at a time.
Definition: Thread.h:290
virtual void v_SetNumWorkers(const unsigned int num)=0
~ThreadMaster()
Destructor.
Definition: Thread.cpp:119
void SetThreadingType(const std::string &p_type)
Sets what ThreadManagers will be created in CreateInstance.
Definition: Thread.cpp:143
ThreadManagerSharedPtr CreateInstance(const ThreadManagerName t, unsigned int nThr)
Creates an instance of a ThreadManager (which one is determined by a previous call to SetThreadingTyp...
Definition: Thread.cpp:179
ThreadManagerSharedPtr & GetInstance(const ThreadManagerName t)
Gets the ThreadManager associated with string s.
Definition: Thread.cpp:163
std::shared_mutex m_mutex
Definition: Thread.h:400
ThreadMaster()
Constructor.
Definition: Thread.cpp:110
std::vector< ThreadManagerSharedPtr > m_threadManagers
Definition: Thread.h:399
std::string m_threadingType
Definition: Thread.h:401
A default ThreadManager.
Definition: Thread.h:436
unsigned int v_GetWorkerNum() override
Definition: Thread.cpp:235
ThreadStartupManager & operator=(const ThreadStartupManager &src)
ThreadDefaultManager copy constructor.
Definition: Thread.cpp:326
unsigned int v_GetMaxNumWorkers() override
Definition: Thread.cpp:260
ThreadStartupManager()
ThreadDefaultManager.
Definition: Thread.cpp:192
void v_SetSchedType(SchedType s) override
Definition: Thread.cpp:285
void v_QueueJob(ThreadJob *job) override
Definition: Thread.cpp:218
ThreadStartupManager(const ThreadStartupManager &src)=default
void v_QueueJobs(std::vector< ThreadJob * > &joblist) override
Definition: Thread.cpp:208
unsigned int v_GetNumWorkers() override
Definition: Thread.cpp:227
void v_SetChunkSize(unsigned int chnk) override
Definition: Thread.cpp:276
const std::string & v_GetType() const override
Definition: Thread.cpp:318
SchedType
Identifies the algorithm for scheduling.
Definition: Thread.h:63
std::shared_lock< std::shared_mutex > ReadLock
Definition: Thread.h:377
std::unique_lock< std::shared_mutex > WriteLock
Definition: Thread.h:376
LibUtilities::NekFactory< std::string, ThreadManager, unsigned int > ThreadManagerFactory
Definition: Thread.h:71
ThreadManagerFactory & GetThreadManagerFactory()
Definition: Thread.cpp:45
std::shared_ptr< ThreadManager > ThreadManagerSharedPtr
Definition: Thread.h:69
ThreadMaster & GetThreadMaster()
Definition: Thread.cpp:128