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