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