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