1/*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkRunnable.h"
9#include "SkThreadPool.h"
10#include "SkThreadUtils.h"
11#include "SkTypes.h"
12
13#if defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_ANDROID)
14#include <unistd.h>
15#endif
16
17// Returns the number of cores on this machine.
18static int num_cores() {
19#if defined(SK_BUILD_FOR_WIN32)
20    SYSTEM_INFO sysinfo;
21    GetSystemInfo(&sysinfo);
22    return sysinfo.dwNumberOfProcessors;
23#elif defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_ANDROID)
24    return sysconf(_SC_NPROCESSORS_ONLN);
25#else
26    return 1;
27#endif
28}
29
30SkThreadPool::SkThreadPool(int count)
31: fState(kRunning_State), fBusyThreads(0) {
32    if (count < 0) count = num_cores();
33    // Create count threads, all running SkThreadPool::Loop.
34    for (int i = 0; i < count; i++) {
35        SkThread* thread = SkNEW_ARGS(SkThread, (&SkThreadPool::Loop, this));
36        *fThreads.append() = thread;
37        thread->start();
38    }
39}
40
41SkThreadPool::~SkThreadPool() {
42    if (kRunning_State == fState) {
43        this->wait();
44    }
45}
46
47void SkThreadPool::wait() {
48    fReady.lock();
49    fState = kWaiting_State;
50    fReady.broadcast();
51    fReady.unlock();
52
53    // Wait for all threads to stop.
54    for (int i = 0; i < fThreads.count(); i++) {
55        fThreads[i]->join();
56        SkDELETE(fThreads[i]);
57    }
58    SkASSERT(fQueue.isEmpty());
59}
60
61/*static*/ void SkThreadPool::Loop(void* arg) {
62    // The SkThreadPool passes itself as arg to each thread as they're created.
63    SkThreadPool* pool = static_cast<SkThreadPool*>(arg);
64
65    while (true) {
66        // We have to be holding the lock to read the queue and to call wait.
67        pool->fReady.lock();
68        while(pool->fQueue.isEmpty()) {
69            // Does the client want to stop and are all the threads ready to stop?
70            // If so, we move into the halting state, and whack all the threads so they notice.
71            if (kWaiting_State == pool->fState && pool->fBusyThreads == 0) {
72                pool->fState = kHalting_State;
73                pool->fReady.broadcast();
74            }
75            // Any time we find ourselves in the halting state, it's quitting time.
76            if (kHalting_State == pool->fState) {
77                pool->fReady.unlock();
78                return;
79            }
80            // wait yields the lock while waiting, but will have it again when awoken.
81            pool->fReady.wait();
82        }
83        // We've got the lock back here, no matter if we ran wait or not.
84
85        // The queue is not empty, so we have something to run.  Claim it.
86        LinkedRunnable* r = pool->fQueue.tail();
87
88        pool->fQueue.remove(r);
89
90        // Having claimed our SkRunnable, we now give up the lock while we run it.
91        // Otherwise, we'd only ever do work on one thread at a time, which rather
92        // defeats the point of this code.
93        pool->fBusyThreads++;
94        pool->fReady.unlock();
95
96        // OK, now really do the work.
97        r->fRunnable->run();
98        SkDELETE(r);
99
100        // Let everyone know we're not busy.
101        pool->fReady.lock();
102        pool->fBusyThreads--;
103        pool->fReady.unlock();
104    }
105
106    SkASSERT(false); // Unreachable.  The only exit happens when pool->fState is kHalting_State.
107}
108
109void SkThreadPool::add(SkRunnable* r) {
110    if (NULL == r) {
111        return;
112    }
113
114    // If we don't have any threads, obligingly just run the thing now.
115    if (fThreads.isEmpty()) {
116        return r->run();
117    }
118
119    // We have some threads.  Queue it up!
120    fReady.lock();
121    SkASSERT(fState != kHalting_State);  // Shouldn't be able to add work when we're halting.
122    LinkedRunnable* linkedRunnable = SkNEW(LinkedRunnable);
123    linkedRunnable->fRunnable = r;
124    fQueue.addToHead(linkedRunnable);
125    fReady.signal();
126    fReady.unlock();
127}
128