/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ ThreadPoolJob::ThreadPoolJob (const String& name) : jobName (name), pool (nullptr), shouldStop (false), isActive (false), shouldBeDeleted (false) { } ThreadPoolJob::~ThreadPoolJob() { // you mustn't delete a job while it's still in a pool! Use ThreadPool::removeJob() // to remove it first! jassert (pool == nullptr || ! pool->contains (this)); } String ThreadPoolJob::getJobName() const { return jobName; } void ThreadPoolJob::setJobName (const String& newName) { jobName = newName; } void ThreadPoolJob::signalJobShouldExit() { shouldStop = true; } //============================================================================== class ThreadPool::ThreadPoolThread : public Thread { public: ThreadPoolThread (ThreadPool& pool_) : Thread ("Pool"), pool (pool_) { } void run() { while (! threadShouldExit()) { if (! pool.runNextJob()) wait (500); } } private: ThreadPool& pool; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread); }; //============================================================================== ThreadPool::ThreadPool (const int numThreads) { jassert (numThreads > 0); // not much point having a pool without any threads! createThreads (numThreads); } ThreadPool::ThreadPool() { createThreads (SystemStats::getNumCpus()); } ThreadPool::~ThreadPool() { removeAllJobs (true, 5000); stopThreads(); } void ThreadPool::createThreads (int numThreads) { for (int i = jmax (1, numThreads); --i >= 0;) threads.add (new ThreadPoolThread (*this)); for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->startThread(); } void ThreadPool::stopThreads() { for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->signalThreadShouldExit(); for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->stopThread (500); } void ThreadPool::addJob (ThreadPoolJob* const job, const bool deleteJobWhenFinished) { jassert (job != nullptr); jassert (job->pool == nullptr); if (job->pool == nullptr) { job->pool = this; job->shouldStop = false; job->isActive = false; job->shouldBeDeleted = deleteJobWhenFinished; { const ScopedLock sl (lock); jobs.add (job); } for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->notify(); } } int ThreadPool::getNumJobs() const { return jobs.size(); } ThreadPoolJob* ThreadPool::getJob (const int index) const { const ScopedLock sl (lock); return jobs [index]; } bool ThreadPool::contains (const ThreadPoolJob* const job) const { const ScopedLock sl (lock); return jobs.contains (const_cast (job)); } bool ThreadPool::isJobRunning (const ThreadPoolJob* const job) const { const ScopedLock sl (lock); return jobs.contains (const_cast (job)) && job->isActive; } bool ThreadPool::waitForJobToFinish (const ThreadPoolJob* const job, const int timeOutMs) const { if (job != nullptr) { const uint32 start = Time::getMillisecondCounter(); while (contains (job)) { if (timeOutMs >= 0 && Time::getMillisecondCounter() >= start + timeOutMs) return false; jobFinishedSignal.wait (2); } } return true; } bool ThreadPool::removeJob (ThreadPoolJob* const job, const bool interruptIfRunning, const int timeOutMs) { bool dontWait = true; OwnedArray deletionList; if (job != nullptr) { const ScopedLock sl (lock); if (jobs.contains (job)) { if (job->isActive) { if (interruptIfRunning) job->signalJobShouldExit(); dontWait = false; } else { jobs.removeValue (job); addToDeleteList (deletionList, job); } } } return dontWait || waitForJobToFinish (job, timeOutMs); } bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeOutMs, ThreadPool::JobSelector* selectedJobsToRemove) { Array jobsToWaitFor; { OwnedArray deletionList; { const ScopedLock sl (lock); for (int i = jobs.size(); --i >= 0;) { ThreadPoolJob* const job = jobs.getUnchecked(i); if (selectedJobsToRemove == nullptr || selectedJobsToRemove->isJobSuitable (job)) { if (job->isActive) { jobsToWaitFor.add (job); if (interruptRunningJobs) job->signalJobShouldExit(); } else { jobs.remove (i); addToDeleteList (deletionList, job); } } } } } const uint32 start = Time::getMillisecondCounter(); for (;;) { for (int i = jobsToWaitFor.size(); --i >= 0;) { ThreadPoolJob* const job = jobsToWaitFor.getUnchecked (i); if (! isJobRunning (job)) jobsToWaitFor.remove (i); } if (jobsToWaitFor.size() == 0) break; if (timeOutMs >= 0 && Time::getMillisecondCounter() >= start + timeOutMs) return false; jobFinishedSignal.wait (20); } return true; } StringArray ThreadPool::getNamesOfAllJobs (const bool onlyReturnActiveJobs) const { StringArray s; const ScopedLock sl (lock); for (int i = 0; i < jobs.size(); ++i) { const ThreadPoolJob* const job = jobs.getUnchecked(i); if (job->isActive || ! onlyReturnActiveJobs) s.add (job->getJobName()); } return s; } bool ThreadPool::setThreadPriorities (const int newPriority) { bool ok = true; for (int i = threads.size(); --i >= 0;) if (! threads.getUnchecked(i)->setPriority (newPriority)) ok = false; return ok; } ThreadPoolJob* ThreadPool::pickNextJobToRun() { OwnedArray deletionList; { const ScopedLock sl (lock); for (int i = 0; i < jobs.size(); ++i) { ThreadPoolJob* job = jobs[i]; if (job != nullptr && ! job->isActive) { if (job->shouldStop) { jobs.remove (i); addToDeleteList (deletionList, job); --i; continue; } job->isActive = true; return job; } } } return nullptr; } bool ThreadPool::runNextJob() { ThreadPoolJob* const job = pickNextJobToRun(); if (job == nullptr) return false; ThreadPoolJob::JobStatus result = ThreadPoolJob::jobHasFinished; JUCE_TRY { result = job->runJob(); } JUCE_CATCH_ALL_ASSERT OwnedArray deletionList; { const ScopedLock sl (lock); if (jobs.contains (job)) { job->isActive = false; if (result != ThreadPoolJob::jobNeedsRunningAgain || job->shouldStop) { jobs.removeValue (job); addToDeleteList (deletionList, job); jobFinishedSignal.signal(); } else { // move the job to the end of the queue if it wants another go jobs.move (jobs.indexOf (job), -1); } } } return true; } void ThreadPool::addToDeleteList (OwnedArray& deletionList, ThreadPoolJob* const job) const { job->shouldStop = true; job->pool = nullptr; if (job->shouldBeDeleted) deletionList.add (job); }