• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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 "include/core/SkExecutor.h"
9 #include "include/private/SkMutex.h"
10 #include "include/private/SkSemaphore.h"
11 #include "include/private/SkSpinlock.h"
12 #include "include/private/SkTArray.h"
13 #include "src/core/SkMakeUnique.h"
14 #include <deque>
15 #include <thread>
16 
17 #if defined(SK_BUILD_FOR_WIN)
18     #include "src/core/SkLeanWindows.h"
num_cores()19     static int num_cores() {
20         SYSTEM_INFO sysinfo;
21         GetNativeSystemInfo(&sysinfo);
22         return (int)sysinfo.dwNumberOfProcessors;
23     }
24 #else
25     #include <unistd.h>
num_cores()26     static int num_cores() {
27         return (int)sysconf(_SC_NPROCESSORS_ONLN);
28     }
29 #endif
30 
~SkExecutor()31 SkExecutor::~SkExecutor() {}
32 
33 // The default default SkExecutor is an SkTrivialExecutor, which just runs the work right away.
34 class SkTrivialExecutor final : public SkExecutor {
add(std::function<void (void)> work)35     void add(std::function<void(void)> work) override {
36         work();
37     }
38 };
39 
40 static SkExecutor* gDefaultExecutor = nullptr;
41 
SetDefaultTrivialExecutor()42 void SetDefaultTrivialExecutor() {
43     static SkTrivialExecutor *gTrivial = new SkTrivialExecutor();
44     gDefaultExecutor = gTrivial;
45 }
GetDefault()46 SkExecutor& SkExecutor::GetDefault() {
47     if (!gDefaultExecutor) {
48         SetDefaultTrivialExecutor();
49     }
50     return *gDefaultExecutor;
51 }
SetDefault(SkExecutor * executor)52 void SkExecutor::SetDefault(SkExecutor* executor) {
53     if (executor) {
54         gDefaultExecutor = executor;
55     } else {
56         SetDefaultTrivialExecutor();
57     }
58 }
59 
60 // We'll always push_back() new work, but pop from the front of deques or the back of SkTArray.
pop(std::deque<std::function<void (void)>> * list)61 static inline std::function<void(void)> pop(std::deque<std::function<void(void)>>* list) {
62     std::function<void(void)> fn = std::move(list->front());
63     list->pop_front();
64     return fn;
65 }
pop(SkTArray<std::function<void (void)>> * list)66 static inline std::function<void(void)> pop(SkTArray<std::function<void(void)>>* list) {
67     std::function<void(void)> fn = std::move(list->back());
68     list->pop_back();
69     return fn;
70 }
71 
72 // An SkThreadPool is an executor that runs work on a fixed pool of OS threads.
73 template <typename WorkList>
74 class SkThreadPool final : public SkExecutor {
75 public:
SkThreadPool(int threads)76     explicit SkThreadPool(int threads) {
77         for (int i = 0; i < threads; i++) {
78             fThreads.emplace_back(&Loop, this);
79         }
80     }
81 
~SkThreadPool()82     ~SkThreadPool() override {
83         // Signal each thread that it's time to shut down.
84         for (int i = 0; i < fThreads.count(); i++) {
85             this->add(nullptr);
86         }
87         // Wait for each thread to shut down.
88         for (int i = 0; i < fThreads.count(); i++) {
89             fThreads[i].join();
90         }
91     }
92 
add(std::function<void (void)> work)93     virtual void add(std::function<void(void)> work) override {
94         // Add some work to our pile of work to do.
95         {
96             SkAutoMutexExclusive lock(fWorkLock);
97             fWork.emplace_back(std::move(work));
98         }
99         // Tell the Loop() threads to pick it up.
100         fWorkAvailable.signal(1);
101     }
102 
borrow()103     virtual void borrow() override {
104         // If there is work waiting, do it.
105         if (fWorkAvailable.try_wait()) {
106             SkAssertResult(this->do_work());
107         }
108     }
109 
110 private:
111     // This method should be called only when fWorkAvailable indicates there's work to do.
do_work()112     bool do_work() {
113         std::function<void(void)> work;
114         {
115             SkAutoMutexExclusive lock(fWorkLock);
116             SkASSERT(!fWork.empty());        // TODO: if (fWork.empty()) { return true; } ?
117             work = pop(&fWork);
118         }
119 
120         if (!work) {
121             return false;  // This is Loop()'s signal to shut down.
122         }
123 
124         work();
125         return true;
126     }
127 
Loop(void * ctx)128     static void Loop(void* ctx) {
129         auto pool = (SkThreadPool*)ctx;
130         do {
131             pool->fWorkAvailable.wait();
132         } while (pool->do_work());
133     }
134 
135     // Both SkMutex and SkSpinlock can work here.
136     using Lock = SkMutex;
137 
138     SkTArray<std::thread> fThreads;
139     WorkList              fWork;
140     Lock                  fWorkLock;
141     SkSemaphore           fWorkAvailable;
142 };
143 
MakeFIFOThreadPool(int threads)144 std::unique_ptr<SkExecutor> SkExecutor::MakeFIFOThreadPool(int threads) {
145     using WorkList = std::deque<std::function<void(void)>>;
146     return skstd::make_unique<SkThreadPool<WorkList>>(threads > 0 ? threads : num_cores());
147 }
MakeLIFOThreadPool(int threads)148 std::unique_ptr<SkExecutor> SkExecutor::MakeLIFOThreadPool(int threads) {
149     using WorkList = SkTArray<std::function<void(void)>>;
150     return skstd::make_unique<SkThreadPool<WorkList>>(threads > 0 ? threads : num_cores());
151 }
152