/src/FreeImage/Source/OpenEXR/IlmThread/IlmThreadPool.cpp
https://bitbucket.org/cabalistic/ogredeps/ · C++ · 456 lines · 231 code · 105 blank · 120 comment · 19 complexity · 701616dd87e9de39df70949f659f20f9 MD5 · raw file
- ///////////////////////////////////////////////////////////////////////////
- //
- // Copyright (c) 2005, Industrial Light & Magic, a division of Lucas
- // Digital Ltd. LLC
- //
- // All rights reserved.
- //
- // Redistribution and use in source and binary forms, with or without
- // modification, are permitted provided that the following conditions are
- // met:
- // * Redistributions of source code must retain the above copyright
- // notice, this list of conditions and the following disclaimer.
- // * Redistributions in binary form must reproduce the above
- // copyright notice, this list of conditions and the following disclaimer
- // in the documentation and/or other materials provided with the
- // distribution.
- // * Neither the name of Industrial Light & Magic nor the names of
- // its contributors may be used to endorse or promote products derived
- // from this software without specific prior written permission.
- //
- // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- //
- ///////////////////////////////////////////////////////////////////////////
- //-----------------------------------------------------------------------------
- //
- // class Task, class ThreadPool, class TaskGroup
- //
- //-----------------------------------------------------------------------------
- #include "IlmThread.h"
- #include "IlmThreadMutex.h"
- #include "IlmThreadSemaphore.h"
- #include "IlmThreadPool.h"
- #include "Iex.h"
- #include <list>
- using namespace std;
- namespace IlmThread {
- namespace {
- class WorkerThread: public Thread
- {
- public:
- WorkerThread (ThreadPool::Data* data);
- virtual void run ();
-
- private:
- ThreadPool::Data * _data;
- };
- } //namespace
- struct TaskGroup::Data
- {
- Data ();
- ~Data ();
-
- void addTask () ;
- void removeTask ();
-
- Semaphore isEmpty; // used to signal that the taskgroup is empty
- int numPending; // number of pending tasks to still execute
- };
- struct ThreadPool::Data
- {
- Data ();
- ~Data();
-
- void finish ();
- bool stopped () const;
- void stop ();
- Semaphore taskSemaphore; // threads wait on this for ready tasks
- Mutex taskMutex; // mutual exclusion for the tasks list
- list<Task*> tasks; // the list of tasks to execute
- size_t numTasks; // fast access to list size
- // (list::size() can be O(n))
- Semaphore threadSemaphore; // signaled when a thread starts executing
- Mutex threadMutex; // mutual exclusion for threads list
- list<WorkerThread*> threads; // the list of all threads
- size_t numThreads; // fast access to list size
-
- bool stopping; // flag indicating whether to stop threads
- Mutex stopMutex; // mutual exclusion for stopping flag
- };
- //
- // class WorkerThread
- //
- WorkerThread::WorkerThread (ThreadPool::Data* data):
- _data (data)
- {
- start();
- }
- void
- WorkerThread::run ()
- {
- //
- // Signal that the thread has started executing
- //
- _data->threadSemaphore.post();
- while (true)
- {
- //
- // Wait for a task to become available
- //
- _data->taskSemaphore.wait();
- {
- Lock taskLock (_data->taskMutex);
-
- //
- // If there is a task pending, pop off the next task in the FIFO
- //
- if (_data->numTasks > 0)
- {
- Task* task = _data->tasks.front();
- TaskGroup* taskGroup = task->group();
- _data->tasks.pop_front();
- _data->numTasks--;
- taskLock.release();
- task->execute();
- taskLock.acquire();
- delete task;
- taskGroup->_data->removeTask();
- }
- else if (_data->stopped())
- {
- break;
- }
- }
- }
- }
- //
- // struct TaskGroup::Data
- //
- TaskGroup::Data::Data (): isEmpty (1), numPending (0)
- {
- // empty
- }
- TaskGroup::Data::~Data ()
- {
- //
- // A TaskGroup acts like an "inverted" semaphore: if the count
- // is above 0 then waiting on the taskgroup will block. This
- // destructor waits until the taskgroup is empty before returning.
- //
- isEmpty.wait ();
- }
- void
- TaskGroup::Data::addTask ()
- {
- //
- // Any access to the taskgroup is protected by a mutex that is
- // held by the threadpool. Therefore it is safe to access
- // numPending before we wait on the semaphore.
- //
- if (numPending++ == 0)
- isEmpty.wait ();
- }
- void
- TaskGroup::Data::removeTask ()
- {
- if (--numPending == 0)
- isEmpty.post ();
- }
-
- //
- // struct ThreadPool::Data
- //
- ThreadPool::Data::Data (): numTasks (0), numThreads (0), stopping (false)
- {
- // empty
- }
- ThreadPool::Data::~Data()
- {
- Lock lock (threadMutex);
- finish ();
- }
- void
- ThreadPool::Data::finish ()
- {
- stop();
- //
- // Signal enough times to allow all threads to stop.
- //
- // Wait until all threads have started their run functions.
- // If we do not wait before we destroy the threads then it's
- // possible that the threads have not yet called their run
- // functions.
- // If this happens then the run function will be called off
- // of an invalid object and we will crash, most likely with
- // an error like: "pure virtual method called"
- //
- for (size_t i = 0; i < numThreads; i++)
- {
- taskSemaphore.post();
- threadSemaphore.wait();
- }
- //
- // Join all the threads
- //
- for (list<WorkerThread*>::iterator i = threads.begin();
- i != threads.end();
- ++i)
- {
- delete (*i);
- }
- Lock lock1 (taskMutex);
- Lock lock2 (stopMutex);
- threads.clear();
- tasks.clear();
- numThreads = 0;
- numTasks = 0;
- stopping = false;
- }
- bool
- ThreadPool::Data::stopped () const
- {
- Lock lock (stopMutex);
- return stopping;
- }
- void
- ThreadPool::Data::stop ()
- {
- Lock lock (stopMutex);
- stopping = true;
- }
- //
- // class Task
- //
- Task::Task (TaskGroup* g): _group(g)
- {
- // empty
- }
- Task::~Task()
- {
- // empty
- }
- TaskGroup*
- Task::group ()
- {
- return _group;
- }
- TaskGroup::TaskGroup ():
- _data (new Data())
- {
- // empty
- }
- TaskGroup::~TaskGroup ()
- {
- delete _data;
- }
- //
- // class ThreadPool
- //
- ThreadPool::ThreadPool (unsigned nthreads):
- _data (new Data())
- {
- setNumThreads (nthreads);
- }
- ThreadPool::~ThreadPool ()
- {
- delete _data;
- }
- int
- ThreadPool::numThreads () const
- {
- Lock lock (_data->threadMutex);
- return _data->numThreads;
- }
- void
- ThreadPool::setNumThreads (int count)
- {
- if (count < 0)
- throw Iex::ArgExc ("Attempt to set the number of threads "
- "in a thread pool to a negative value.");
- //
- // Lock access to thread list and size
- //
- Lock lock (_data->threadMutex);
- if ((size_t)count > _data->numThreads)
- {
- //
- // Add more threads
- //
- while (_data->numThreads < (size_t)count)
- {
- _data->threads.push_back (new WorkerThread (_data));
- _data->numThreads++;
- }
- }
- else if ((size_t)count < _data->numThreads)
- {
- //
- // Wait until all existing threads are finished processing,
- // then delete all threads.
- //
- _data->finish ();
- //
- // Add in new threads
- //
- while (_data->numThreads < (size_t)count)
- {
- _data->threads.push_back (new WorkerThread (_data));
- _data->numThreads++;
- }
- }
- }
- void
- ThreadPool::addTask (Task* task)
- {
- //
- // Lock the threads, needed to access numThreads
- //
- Lock lock (_data->threadMutex);
- if (_data->numThreads == 0)
- {
- task->execute ();
- delete task;
- }
- else
- {
- //
- // Get exclusive access to the tasks queue
- //
- {
- Lock taskLock (_data->taskMutex);
- //
- // Push the new task into the FIFO
- //
- _data->tasks.push_back (task);
- _data->numTasks++;
- task->group()->_data->addTask();
- }
-
- //
- // Signal that we have a new task to process
- //
- _data->taskSemaphore.post ();
- }
- }
- ThreadPool&
- ThreadPool::globalThreadPool ()
- {
- //
- // The global thread pool
- //
-
- static ThreadPool gThreadPool (0);
- return gThreadPool;
- }
- void
- ThreadPool::addGlobalTask (Task* task)
- {
- globalThreadPool().addTask (task);
- }
- } // namespace IlmThread