Line data Source code
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 "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 0 : static int num_cores() {
26 0 : return (int)sysconf(_SC_NPROCESSORS_ONLN);
27 : }
28 : #endif
29 :
30 0 : SkExecutor::~SkExecutor() {}
31 :
32 : // The default default SkExecutor is an SkTrivialExecutor, which just runs the work right away.
33 0 : class SkTrivialExecutor final : public SkExecutor {
34 0 : void add(std::function<void(void)> work) override {
35 0 : work();
36 0 : }
37 : };
38 :
39 3 : static SkTrivialExecutor gTrivial;
40 : static SkExecutor* gDefaultExecutor = &gTrivial;
41 :
42 0 : SkExecutor& SkExecutor::GetDefault() {
43 0 : return *gDefaultExecutor;
44 : }
45 0 : void SkExecutor::SetDefault(SkExecutor* executor) {
46 0 : gDefaultExecutor = executor ? executor : &gTrivial;
47 0 : }
48 :
49 : // An SkThreadPool is an executor that runs work on a fixed pool of OS threads.
50 : class SkThreadPool final : public SkExecutor {
51 : public:
52 0 : explicit SkThreadPool(int threads) {
53 0 : for (int i = 0; i < threads; i++) {
54 0 : fThreads.emplace_back(new SkThread(&Loop, this));
55 0 : fThreads.back()->start();
56 : }
57 0 : }
58 :
59 0 : ~SkThreadPool() override {
60 : // Signal each thread that it's time to shut down.
61 0 : for (int i = 0; i < fThreads.count(); i++) {
62 0 : this->add(nullptr);
63 : }
64 : // Wait for each thread to shut down.
65 0 : for (int i = 0; i < fThreads.count(); i++) {
66 0 : fThreads[i]->join();
67 : }
68 0 : }
69 :
70 0 : virtual void add(std::function<void(void)> work) override {
71 : // Add some work to our pile of work to do.
72 : {
73 0 : SkAutoExclusive lock(fWorkLock);
74 0 : fWork.emplace_back(std::move(work));
75 : }
76 : // Tell the Loop() threads to pick it up.
77 0 : fWorkAvailable.signal(1);
78 0 : }
79 :
80 0 : virtual void borrow() override {
81 : // If there is work waiting, do it.
82 0 : if (fWorkAvailable.try_wait()) {
83 0 : SkAssertResult(this->do_work());
84 : }
85 0 : }
86 :
87 : private:
88 : // This method should be called only when fWorkAvailable indicates there's work to do.
89 0 : bool do_work() {
90 0 : std::function<void(void)> work;
91 : {
92 0 : SkAutoExclusive lock(fWorkLock);
93 0 : SkASSERT(!fWork.empty()); // TODO: if (fWork.empty()) { return true; } ?
94 0 : work = std::move(fWork.back());
95 0 : fWork.pop_back();
96 : }
97 :
98 0 : if (!work) {
99 0 : return false; // This is Loop()'s signal to shut down.
100 : }
101 :
102 0 : work();
103 0 : return true;
104 : }
105 :
106 0 : static void Loop(void* ctx) {
107 0 : auto pool = (SkThreadPool*)ctx;
108 0 : do {
109 0 : pool->fWorkAvailable.wait();
110 : } while (pool->do_work());
111 0 : }
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 :
122 0 : std::unique_ptr<SkExecutor> SkExecutor::MakeThreadPool(int threads) {
123 0 : return skstd::make_unique<SkThreadPool>(threads > 0 ? threads : num_cores());
124 : }
|