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