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 | |
30 | SkExecutor::~SkExecutor() {} |
31 | |
32 | // The default default SkExecutor is an SkTrivialExecutor, which just runs the work right away. |
33 | class SkTrivialExecutor final : public SkExecutor { |
34 | void add(std::function<void(void)> work) override { |
35 | work(); |
36 | } |
37 | }; |
38 | |
39 | static SkExecutor* gDefaultExecutor = nullptr; |
40 | |
41 | void SetDefaultTrivialExecutor() { |
42 | static SkTrivialExecutor *gTrivial = new SkTrivialExecutor(); |
43 | gDefaultExecutor = gTrivial; |
44 | } |
45 | SkExecutor& SkExecutor::GetDefault() { |
46 | if (!gDefaultExecutor) { |
47 | SetDefaultTrivialExecutor(); |
48 | } |
49 | return *gDefaultExecutor; |
50 | } |
51 | void 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. |
60 | static 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 | } |
65 | static 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. |
72 | template <typename WorkList> |
73 | class SkThreadPool final : public SkExecutor { |
74 | public: |
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 | |
109 | private: |
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 | |
143 | std::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 | } |
147 | std::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 |