|
|
|||
File indexing completed on 2026-09-12 09:18:25
0001 // Created by: Kirill Gavrilov 0002 // Copyright (c) 2017-2019 OPEN CASCADE SAS 0003 // 0004 // This file is part of Open CASCADE Technology software library. 0005 // 0006 // This library is free software; you can redistribute it and/or modify it under 0007 // the terms of the GNU Lesser General Public License version 2.1 as published 0008 // by the Free Software Foundation, with special exception defined in the file 0009 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT 0010 // distribution for complete text of the license and disclaimer of any warranty. 0011 // 0012 // Alternatively, this file may be used under the terms of Open CASCADE 0013 // commercial license or contractual agreement. 0014 0015 #ifndef _OSD_ThreadPool_HeaderFile 0016 #define _OSD_ThreadPool_HeaderFile 0017 0018 #include <NCollection_Array1.hxx> 0019 #include <OSD_Thread.hxx> 0020 #include <Standard_Condition.hxx> 0021 0022 #include <Standard_ProgramError.hxx> 0023 0024 #include <atomic> 0025 #include <optional> 0026 0027 //! Class defining a thread pool for executing algorithms in multi-threaded mode. 0028 //! Thread pool allocates requested amount of threads and keep them alive 0029 //! (in sleep mode when unused) during thread pool lifetime. 0030 //! The same pool can be used by multiple consumers, 0031 //! including nested multi-threading algorithms and concurrent threads: 0032 //! - Thread pool can be used either by multi-threaded algorithm by creating 0033 //! OSD_ThreadPool::Launcher. 0034 //! The functor performing a job takes two parameters - Thread Index and Data Index: 0035 //! void operator(int theThreadIndex, int theDataIndex){} 0036 //! Multi-threaded algorithm may rely on Thread Index for allocating thread-local variables in 0037 //! array form, since the Thread Index is guaranteed to be within range OSD_ThreadPool::Lower() 0038 //! and OSD_ThreadPool::Upper(). 0039 //! - Default thread pool (OSD_ThreadPool::DefaultPool()) can be used in general case, 0040 //! but application may prefer creating a dedicated pool for better control. 0041 //! - Default thread pool allocates the amount of threads considering concurrency 0042 //! level of the system (amount of logical processors). 0043 //! This can be overridden during OSD_ThreadPool construction or by calling OSD_ThreadPool::Init() 0044 //! (the pool should not be used!). 0045 //! - OSD_ThreadPool::Launcher reserves specific amount of threads from the pool for executing 0046 //! multi-threaded Job. 0047 //! Normally, single Launcher instance will occupy all threads available in thread pool, 0048 //! so that nested multi-threaded algorithms (within the same thread) 0049 //! and concurrent threads trying to use the same thread pool will run sequentially. 0050 //! This behavior is affected by OSD_ThreadPool::NbDefaultThreadsToLaunch() parameter 0051 //! and Launcher constructor, so that single Launcher instance will occupy not all threads 0052 //! in the pool allowing other threads to be used concurrently. 0053 //! - OSD_ThreadPool::Launcher locks thread one-by-one from thread pool in a thread-safe way. 0054 //! - Each working thread catches exceptions occurred during job execution, and Launcher will 0055 //! throw Standard_Failure in a caller thread on completed execution. 0056 class OSD_ThreadPool : public Standard_Transient 0057 { 0058 DEFINE_STANDARD_RTTIEXT(OSD_ThreadPool, Standard_Transient) 0059 public: 0060 //! Return (or create) a default thread pool. 0061 //! Number of threads argument will be considered only when called first time. 0062 Standard_EXPORT static const occ::handle<OSD_ThreadPool>& DefaultPool(int theNbThreads = -1); 0063 0064 public: 0065 //! Main constructor. 0066 //! Application may consider specifying more threads than actually 0067 //! available (OSD_Parallel::NbLogicalProcessors()) and set up NbDefaultThreadsToLaunch() to a 0068 //! smaller value so that concurrent threads will be able using single Thread Pool instance more 0069 //! efficiently. 0070 //! @param theNbThreads threads number to be created by pool 0071 //! (if -1 is specified then OSD_Parallel::NbLogicalProcessors() will be used) 0072 Standard_EXPORT OSD_ThreadPool(int theNbThreads = -1); 0073 0074 //! Destructor. 0075 Standard_EXPORT ~OSD_ThreadPool() override; 0076 0077 //! Return TRUE if at least 2 threads are available (including self-thread). 0078 bool HasThreads() const { return NbThreads() >= 2; } 0079 0080 //! Return the lower thread index. 0081 int LowerThreadIndex() const { return 0; } 0082 0083 //! Return the upper thread index (last index is reserved for self-thread). 0084 int UpperThreadIndex() const { return LowerThreadIndex() + myThreads.Length(); } 0085 0086 //! Return the number of threads; >= 1. 0087 int NbThreads() const { return myThreads.Length() + 1; } 0088 0089 //! Return maximum number of threads to be locked by a single Launcher object by default; 0090 //! the entire thread pool size is returned by default. 0091 int NbDefaultThreadsToLaunch() const { return myNbDefThreads; } 0092 0093 //! Set maximum number of threads to be locked by a single Launcher object by default. 0094 //! Should be set BEFORE first usage. 0095 void SetNbDefaultThreadsToLaunch(int theNbThreads) { myNbDefThreads = theNbThreads; } 0096 0097 //! Checks if thread pools has active consumers. 0098 Standard_EXPORT bool IsInUse(); 0099 0100 //! Reinitialize the thread pool with a different number of threads. 0101 //! Should be called only with no active jobs, or exception Standard_ProgramError will be thrown! 0102 Standard_EXPORT void Init(int theNbThreads); 0103 0104 protected: 0105 //! Thread function interface. 0106 class JobInterface 0107 { 0108 public: 0109 virtual void Perform(int theThreadIndex) = 0; 0110 }; 0111 0112 //! Thread with back reference to thread pool and thread index in it. 0113 class EnumeratedThread : public OSD_Thread 0114 { 0115 friend class OSD_ThreadPool; 0116 0117 public: 0118 //! Main constructor. 0119 EnumeratedThread(bool theIsSelfThread = false) 0120 : myPool(nullptr), 0121 myJob(nullptr), 0122 myWakeEvent(false), 0123 myIdleEvent(false), 0124 myThreadIndex(0), 0125 myUsageCounter(0), 0126 myIsStarted(false), 0127 myToCatchFpe(false), 0128 myIsSelfThread(theIsSelfThread) 0129 { 0130 } 0131 0132 //! Occupy this thread for thread pool launcher. 0133 //! @return TRUE on success, or FALSE if thread has been already occupied 0134 Standard_EXPORT bool Lock(); 0135 0136 //! Release this thread for thread pool launcher; should be called only after successful 0137 //! OccupyThread(). 0138 Standard_EXPORT void Free(); 0139 0140 //! Wake up the thread. 0141 Standard_EXPORT void WakeUp(JobInterface* theJob, bool theToCatchFpe); 0142 0143 //! Wait the thread going into Idle state (finished jobs). 0144 Standard_EXPORT void WaitIdle(); 0145 0146 public: 0147 //! Copy constructor. 0148 EnumeratedThread(const EnumeratedThread& theCopy) 0149 : OSD_Thread(theCopy), 0150 myPool(nullptr), 0151 myJob(nullptr), 0152 myWakeEvent(false), 0153 myIdleEvent(false), 0154 myThreadIndex(0), 0155 myUsageCounter(0), 0156 myIsStarted(false), 0157 myToCatchFpe(false), 0158 myIsSelfThread(false) 0159 { 0160 Assign(theCopy); 0161 } 0162 0163 //! Assignment operator. 0164 EnumeratedThread& operator=(const EnumeratedThread& theCopy) 0165 { 0166 Assign(theCopy); 0167 return *this; 0168 } 0169 0170 //! Assignment operator. 0171 void Assign(const EnumeratedThread& theCopy) 0172 { 0173 OSD_Thread::Assign(theCopy); 0174 myPool = theCopy.myPool; 0175 myJob = theCopy.myJob; 0176 myThreadIndex = theCopy.myThreadIndex; 0177 myToCatchFpe = theCopy.myToCatchFpe; 0178 myIsSelfThread = theCopy.myIsSelfThread; 0179 } 0180 0181 private: 0182 //! Method is executed in the context of thread. 0183 void performThread(); 0184 0185 //! Method is executed in the context of thread. 0186 static void* runThread(void* theTask); 0187 0188 private: 0189 OSD_ThreadPool* myPool; 0190 JobInterface* myJob; 0191 std::optional<Standard_ProgramError> myFailure; 0192 Standard_Condition myWakeEvent; 0193 Standard_Condition myIdleEvent; 0194 int myThreadIndex; 0195 std::atomic<int> myUsageCounter; 0196 bool myIsStarted; 0197 bool myToCatchFpe; 0198 bool myIsSelfThread; 0199 }; 0200 0201 public: 0202 //! Launcher object locking a subset of threads (or all threads) 0203 //! in a thread pool to perform parallel execution of the job. 0204 class Launcher 0205 { 0206 public: 0207 //! Lock specified number of threads from the thread pool. 0208 //! If thread pool is already locked by another user, 0209 //! Launcher will lock as many threads as possible 0210 //! (if none will be locked, then single threaded execution will be done). 0211 //! @param thePool thread pool to lock the threads 0212 //! @param theMaxThreads number of threads to lock; 0213 //! -1 specifies that default number of threads 0214 //! to be used OSD_ThreadPool::NbDefaultThreadsToLaunch() 0215 Standard_EXPORT Launcher(OSD_ThreadPool& thePool, int theMaxThreads = -1); 0216 0217 //! Release threads. 0218 ~Launcher() { Release(); } 0219 0220 //! Return TRUE if at least 2 threads have been locked for parallel execution (including 0221 //! self-thread); otherwise, the functor will be executed within the caller thread. 0222 bool HasThreads() const { return myNbThreads >= 2; } 0223 0224 //! Return amount of locked threads; >= 1. 0225 int NbThreads() const { return myNbThreads; } 0226 0227 //! Return the lower thread index. 0228 int LowerThreadIndex() const { return 0; } 0229 0230 //! Return the upper thread index (last index is reserved for the self-thread). 0231 int UpperThreadIndex() const { return LowerThreadIndex() + myNbThreads - 1; } 0232 0233 //! Simple primitive for parallelization of "for" loops, e.g.: 0234 //! @code 0235 //! for (int anIter = theBegin; anIter < theEnd; ++anIter) {} 0236 //! @endcode 0237 //! @param theBegin the first data index (inclusive) 0238 //! @param theEnd the last data index (exclusive) 0239 //! @param theFunctor functor providing an interface 0240 //! "void operator(int theThreadIndex, int theDataIndex){}" performing task 0241 //! for specified index 0242 template <typename Functor> 0243 void Perform(int theBegin, int theEnd, const Functor& theFunctor) 0244 { 0245 JobRange aData(theBegin, theEnd); 0246 Job<Functor> aJob(theFunctor, aData); 0247 perform(aJob); 0248 } 0249 0250 //! Release threads before Launcher destruction. 0251 Standard_EXPORT void Release(); 0252 0253 protected: 0254 //! Execute job. 0255 Standard_EXPORT void perform(JobInterface& theJob); 0256 0257 //! Initialize job and start threads. 0258 Standard_EXPORT void run(JobInterface& theJob); 0259 0260 //! Wait threads execution. 0261 Standard_EXPORT void wait(); 0262 0263 private: 0264 Launcher(const Launcher& theCopy) = delete; 0265 Launcher& operator=(const Launcher& theCopy) = delete; 0266 0267 private: 0268 // clang-format off 0269 NCollection_Array1<EnumeratedThread*> myThreads; //!< array of locked threads (including self-thread) 0270 // clang-format on 0271 EnumeratedThread mySelfThread; 0272 int myNbThreads; //!< amount of locked threads 0273 }; 0274 0275 protected: 0276 //! Auxiliary class which ensures exclusive access to iterators of processed data pool. 0277 class JobRange 0278 { 0279 public: 0280 //! Constructor 0281 JobRange(const int& theBegin, const int& theEnd) 0282 : myBegin(theBegin), 0283 myEnd(theEnd), 0284 myIt(theBegin) 0285 { 0286 } 0287 0288 //! Returns const link on the first element. 0289 const int& Begin() const { return myBegin; } 0290 0291 //! Returns const link on the last element. 0292 const int& End() const { return myEnd; } 0293 0294 //! Returns first non processed element or end. 0295 //! Thread-safe method. 0296 int It() const { return myIt.fetch_add(1); } 0297 0298 private: 0299 JobRange(const JobRange& theCopy) = delete; 0300 JobRange& operator=(const JobRange& theCopy) = delete; 0301 0302 private: 0303 const int& myBegin; //!< First element of range 0304 const int& myEnd; //!< Last element of range 0305 mutable std::atomic<int> myIt; //!< First non processed element of range 0306 }; 0307 0308 //! Auxiliary wrapper class for thread function. 0309 template <typename FunctorT> 0310 class Job : public JobInterface 0311 { 0312 public: 0313 //! Constructor. 0314 Job(const FunctorT& thePerformer, JobRange& theRange) 0315 : myPerformer(thePerformer), 0316 myRange(theRange) 0317 { 0318 } 0319 0320 //! Method is executed in the context of thread. 0321 void Perform(int theThreadIndex) override 0322 { 0323 for (int anIter = myRange.It(); anIter < myRange.End(); anIter = myRange.It()) 0324 { 0325 myPerformer(theThreadIndex, anIter); 0326 } 0327 } 0328 0329 private: 0330 Job(const Job& theCopy) = delete; 0331 Job& operator=(const Job& theCopy) = delete; 0332 0333 private: //! @name private fields 0334 const FunctorT& myPerformer; //!< Link on functor 0335 const JobRange& myRange; //!< Link on processed data block 0336 }; 0337 0338 //! Release threads. 0339 void release(); 0340 0341 //! Perform the job and catch exceptions. 0342 static void performJob(std::optional<Standard_ProgramError>& theFailure, 0343 OSD_ThreadPool::JobInterface* theJob, 0344 int theThreadIndex); 0345 0346 private: 0347 //! This method should not be called (prohibited). 0348 OSD_ThreadPool(const OSD_ThreadPool& theCopy) = delete; 0349 //! This method should not be called (prohibited). 0350 OSD_ThreadPool& operator=(const OSD_ThreadPool& theCopy) = delete; 0351 0352 private: 0353 // clang-format off 0354 NCollection_Array1<EnumeratedThread> myThreads; //!< array of defined threads (excluding self-thread) 0355 // clang-format on 0356 int myNbDefThreads; //!< maximum number of threads to be locked by a single Launcher by default 0357 bool myShutDown; //!< flag to shut down (destroy) the thread pool 0358 }; 0359 0360 #endif // _OSD_ThreadPool_HeaderFile
| [ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
|
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |
|