Back to home page

EIC code displayed by LXR

 
 

    


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