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 "SkExecutor.h"
9 #include "SkMakeUnique.h"
10 #include "SkMutex.h"
11 #include "SkSemaphore.h"
12 #include "SkSpinlock.h"
13 #include "SkTArray.h"
14 #include <deque>
15 #include <thread>
16 
17 #if defined(SK_BUILD_FOR_WIN)
18     #include <windows.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 SkTrivialExecutor gTrivial;
41 static SkExecutor* gDefaultExecutor = &gTrivial;
42 
GetDefault()43 SkExecutor& SkExecutor::GetDefault() {
44     return *gDefaultExecutor;
45 }
SetDefault(SkExecutor * executor)46 void SkExecutor::SetDefault(SkExecutor* executor) {
47     gDefaultExecutor = executor ? executor : &gTrivial;
48 }
49 
50 // 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)51 static inline std::function<void(void)> pop(std::deque<std::function<void(void)>>* list) {
52     std::function<void(void)> fn = std::move(list->front());
53     list->pop_front();
54     return fn;
55 }
pop(SkTArray<std::function<void (void)>> * list)56 static inline std::function<void(void)> pop(SkTArray<std::function<void(void)>>* list) {
57     std::function<void(void)> fn = std::move(list->back());
58     list->pop_back();
59     return fn;
60 }
61 
62 // An SkThreadPool is an executor that runs work on a fixed pool of OS threads.
63 template <typename WorkList>
64 class SkThreadPool final : public SkExecutor {
65 public:
SkThreadPool(int threads)66     explicit SkThreadPool(int threads) {
67         for (int i = 0; i < threads; i++) {
68             fThreads.emplace_back(&Loop, this);
69         }
70     }
71 
~SkThreadPool()72     ~SkThreadPool() override {
73         // Signal each thread that it's time to shut down.
74         for (int i = 0; i < fThreads.count(); i++) {
75             this->add(nullptr);
76         }
77         // Wait for each thread to shut down.
78         for (int i = 0; i < fThreads.count(); i++) {
79             fThreads[i].join();
80         }
81     }
82 
add(std::function<void (void)> work)83     virtual void add(std::function<void(void)> work) override {
84         // Add some work to our pile of work to do.
85         {
86             SkAutoExclusive lock(fWorkLock);
87             fWork.emplace_back(std::move(work));
88         }
89         // Tell the Loop() threads to pick it up.
90         fWorkAvailable.signal(1);
91     }
92 
borrow()93     virtual void borrow() override {
94         // If there is work waiting, do it.
95         if (fWorkAvailable.try_wait()) {
96             SkAssertResult(this->do_work());
97         }
98     }
99 
100 private:
101     // This method should be called only when fWorkAvailable indicates there's work to do.
do_work()102     bool do_work() {
103         std::function<void(void)> work;
104         {
105             SkAutoExclusive lock(fWorkLock);
106             SkASSERT(!fWork.empty());        // TODO: if (fWork.empty()) { return true; } ?
107             work = pop(&fWork);
108         }
109 
110         if (!work) {
111             return false;  // This is Loop()'s signal to shut down.
112         }
113 
114         work();
115         return true;
116     }
117 
Loop(void * ctx)118     static void Loop(void* ctx) {
119         auto pool = (SkThreadPool*)ctx;
120         do {
121             pool->fWorkAvailable.wait();
122         } while (pool->do_work());
123     }
124 
125     // Both SkMutex and SkSpinlock can work here.
126     using Lock = SkMutex;
127 
128     SkTArray<std::thread> fThreads;
129     WorkList              fWork;
130     Lock                  fWorkLock;
131     SkSemaphore           fWorkAvailable;
132 };
133 
MakeFIFOThreadPool(int threads)134 std::unique_ptr<SkExecutor> SkExecutor::MakeFIFOThreadPool(int threads) {
135     using WorkList = std::deque<std::function<void(void)>>;
136     return skstd::make_unique<SkThreadPool<WorkList>>(threads > 0 ? threads : num_cores());
137 }
MakeLIFOThreadPool(int threads)138 std::unique_ptr<SkExecutor> SkExecutor::MakeLIFOThreadPool(int threads) {
139     using WorkList = SkTArray<std::function<void(void)>>;
140     return skstd::make_unique<SkThreadPool<WorkList>>(threads > 0 ? threads : num_cores());
141 }
142