| Index: src/utils/SkThreadPool.h
|
| diff --git a/src/utils/SkThreadPool.h b/src/utils/SkThreadPool.h
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c99c5c4188a5b636f8213ce655f5138fb28f79a0
|
| --- /dev/null
|
| +++ b/src/utils/SkThreadPool.h
|
| @@ -0,0 +1,221 @@
|
| +/*
|
| + * Copyright 2012 Google Inc.
|
| + *
|
| + * Use of this source code is governed by a BSD-style license that can be
|
| + * found in the LICENSE file.
|
| + */
|
| +
|
| +#ifndef SkThreadPool_DEFINED
|
| +#define SkThreadPool_DEFINED
|
| +
|
| +#include "SkCondVar.h"
|
| +#include "SkRunnable.h"
|
| +#include "SkTDArray.h"
|
| +#include "SkTInternalLList.h"
|
| +#include "SkThreadUtils.h"
|
| +#include "SkTypes.h"
|
| +
|
| +#if defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_ANDROID)
|
| +# include <unistd.h>
|
| +#endif
|
| +
|
| +// Returns the number of cores on this machine.
|
| +static inline int num_cores() {
|
| +#if defined(SK_BUILD_FOR_WIN32)
|
| + SYSTEM_INFO sysinfo;
|
| + GetSystemInfo(&sysinfo);
|
| + return sysinfo.dwNumberOfProcessors;
|
| +#elif defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_ANDROID)
|
| + return (int) sysconf(_SC_NPROCESSORS_ONLN);
|
| +#else
|
| + return 1;
|
| +#endif
|
| +}
|
| +
|
| +template <typename T>
|
| +class SkTThreadPool {
|
| +public:
|
| + /**
|
| + * Create a threadpool with count threads, or one thread per core if kThreadPerCore.
|
| + */
|
| + static const int kThreadPerCore = -1;
|
| + explicit SkTThreadPool(int count);
|
| + ~SkTThreadPool();
|
| +
|
| + /**
|
| + * Queues up an SkRunnable to run when a thread is available, or synchronously if count is 0.
|
| + * Does not take ownership. NULL is a safe no-op. If T is not void, the runnable will be passed
|
| + * a reference to a T on the thread's local stack.
|
| + */
|
| + void add(SkTRunnable<T>*);
|
| +
|
| + /**
|
| + * Same as add, but adds the runnable as the very next to run rather than enqueueing it.
|
| + */
|
| + void addNext(SkTRunnable<T>*);
|
| +
|
| + /**
|
| + * Block until all added SkRunnables have completed. Once called, calling add() is undefined.
|
| + */
|
| + void wait();
|
| +
|
| + private:
|
| + struct LinkedRunnable {
|
| + SkTRunnable<T>* fRunnable; // Unowned.
|
| + SK_DECLARE_INTERNAL_LLIST_INTERFACE(LinkedRunnable);
|
| + };
|
| +
|
| + enum State {
|
| + kRunning_State, // Normal case. We've been constructed and no one has called wait().
|
| + kWaiting_State, // wait has been called, but there still might be work to do or being done.
|
| + kHalting_State, // There's no work to do and no thread is busy. All threads can shut down.
|
| + };
|
| +
|
| + void addSomewhere(SkTRunnable<T>* r,
|
| + void (SkTInternalLList<LinkedRunnable>::*)(LinkedRunnable*));
|
| +
|
| + SkTInternalLList<LinkedRunnable> fQueue;
|
| + SkCondVar fReady;
|
| + SkTDArray<SkThread*> fThreads;
|
| + State fState;
|
| + int fBusyThreads;
|
| +
|
| + static void Loop(void*); // Static because we pass in this.
|
| +};
|
| +
|
| +template <typename T>
|
| +SkTThreadPool<T>::SkTThreadPool(int count) : fState(kRunning_State), fBusyThreads(0) {
|
| + if (count < 0) {
|
| + count = num_cores();
|
| + }
|
| + // Create count threads, all running SkTThreadPool::Loop.
|
| + for (int i = 0; i < count; i++) {
|
| + SkThread* thread = SkNEW_ARGS(SkThread, (&SkTThreadPool::Loop, this));
|
| + *fThreads.append() = thread;
|
| + thread->start();
|
| + }
|
| +}
|
| +
|
| +template <typename T>
|
| +SkTThreadPool<T>::~SkTThreadPool() {
|
| + if (kRunning_State == fState) {
|
| + this->wait();
|
| + }
|
| +}
|
| +
|
| +namespace SkThreadPoolPrivate {
|
| +
|
| +template <typename T>
|
| +struct ThreadLocal {
|
| + void run(SkTRunnable<T>* r) { r->run(data); }
|
| + T data;
|
| +};
|
| +
|
| +template <>
|
| +struct ThreadLocal<void> {
|
| + void run(SkTRunnable<void>* r) { r->run(); }
|
| +};
|
| +
|
| +} // namespace SkThreadPoolPrivate
|
| +
|
| +template <typename T>
|
| +void SkTThreadPool<T>::addSomewhere(SkTRunnable<T>* r,
|
| + void (SkTInternalLList<LinkedRunnable>::* f)(LinkedRunnable*)) {
|
| + if (r == NULL) {
|
| + return;
|
| + }
|
| +
|
| + if (fThreads.isEmpty()) {
|
| + SkThreadPoolPrivate::ThreadLocal<T> threadLocal;
|
| + threadLocal.run(r);
|
| + return;
|
| + }
|
| +
|
| + LinkedRunnable* linkedRunnable = SkNEW(LinkedRunnable);
|
| + linkedRunnable->fRunnable = r;
|
| + fReady.lock();
|
| + SkASSERT(fState != kHalting_State); // Shouldn't be able to add work when we're halting.
|
| + (fQueue.*f)(linkedRunnable);
|
| + fReady.signal();
|
| + fReady.unlock();
|
| +}
|
| +
|
| +template <typename T>
|
| +void SkTThreadPool<T>::add(SkTRunnable<T>* r) {
|
| + this->addSomewhere(r, &SkTInternalLList<LinkedRunnable>::addToTail);
|
| +}
|
| +
|
| +template <typename T>
|
| +void SkTThreadPool<T>::addNext(SkTRunnable<T>* r) {
|
| + this->addSomewhere(r, &SkTInternalLList<LinkedRunnable>::addToHead);
|
| +}
|
| +
|
| +
|
| +template <typename T>
|
| +void SkTThreadPool<T>::wait() {
|
| + fReady.lock();
|
| + fState = kWaiting_State;
|
| + fReady.broadcast();
|
| + fReady.unlock();
|
| +
|
| + // Wait for all threads to stop.
|
| + for (int i = 0; i < fThreads.count(); i++) {
|
| + fThreads[i]->join();
|
| + SkDELETE(fThreads[i]);
|
| + }
|
| + SkASSERT(fQueue.isEmpty());
|
| +}
|
| +
|
| +template <typename T>
|
| +/*static*/ void SkTThreadPool<T>::Loop(void* arg) {
|
| + // The SkTThreadPool passes itself as arg to each thread as they're created.
|
| + SkTThreadPool<T>* pool = static_cast<SkTThreadPool<T>*>(arg);
|
| + SkThreadPoolPrivate::ThreadLocal<T> threadLocal;
|
| +
|
| + while (true) {
|
| + // We have to be holding the lock to read the queue and to call wait.
|
| + pool->fReady.lock();
|
| + while(pool->fQueue.isEmpty()) {
|
| + // Does the client want to stop and are all the threads ready to stop?
|
| + // If so, we move into the halting state, and whack all the threads so they notice.
|
| + if (kWaiting_State == pool->fState && pool->fBusyThreads == 0) {
|
| + pool->fState = kHalting_State;
|
| + pool->fReady.broadcast();
|
| + }
|
| + // Any time we find ourselves in the halting state, it's quitting time.
|
| + if (kHalting_State == pool->fState) {
|
| + pool->fReady.unlock();
|
| + return;
|
| + }
|
| + // wait yields the lock while waiting, but will have it again when awoken.
|
| + pool->fReady.wait();
|
| + }
|
| + // We've got the lock back here, no matter if we ran wait or not.
|
| +
|
| + // The queue is not empty, so we have something to run. Claim it.
|
| + LinkedRunnable* r = pool->fQueue.head();
|
| +
|
| + pool->fQueue.remove(r);
|
| +
|
| + // Having claimed our SkRunnable, we now give up the lock while we run it.
|
| + // Otherwise, we'd only ever do work on one thread at a time, which rather
|
| + // defeats the point of this code.
|
| + pool->fBusyThreads++;
|
| + pool->fReady.unlock();
|
| +
|
| + // OK, now really do the work.
|
| + threadLocal.run(r->fRunnable);
|
| + SkDELETE(r);
|
| +
|
| + // Let everyone know we're not busy.
|
| + pool->fReady.lock();
|
| + pool->fBusyThreads--;
|
| + pool->fReady.unlock();
|
| + }
|
| +
|
| + SkASSERT(false); // Unreachable. The only exit happens when pool->fState is kHalting_State.
|
| +}
|
| +
|
| +typedef SkTThreadPool<void> SkThreadPool;
|
| +
|
| +#endif
|
|
|