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" num_cores()18 static int num_cores() { 19 SYSTEM_INFO sysinfo; 20 GetNativeSystemInfo(&sysinfo); 21 return (int)sysinfo.dwNumberOfProcessors; 22 } 23 #else 24 #include <unistd.h> num_cores()25 static int num_cores() { 26 return (int)sysconf(_SC_NPROCESSORS_ONLN); 27 } 28 #endif 29 ~SkExecutor()30SkExecutor::~SkExecutor() {} 31 32 // The default default SkExecutor is an SkTrivialExecutor, which just runs the work right away. 33 class SkTrivialExecutor final : public SkExecutor { add(std::function<void (void)> work)34 void add(std::function<void(void)> work) override { 35 work(); 36 } 37 }; 38 39 static SkExecutor* gDefaultExecutor = nullptr; 40 SetDefaultTrivialExecutor()41void SetDefaultTrivialExecutor() { 42 static SkTrivialExecutor *gTrivial = new SkTrivialExecutor(); 43 gDefaultExecutor = gTrivial; 44 } GetDefault()45SkExecutor& SkExecutor::GetDefault() { 46 if (!gDefaultExecutor) { 47 SetDefaultTrivialExecutor(); 48 } 49 return *gDefaultExecutor; 50 } SetDefault(SkExecutor * executor)51void 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. pop(std::deque<std::function<void (void)>> * list)60static 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 } pop(SkTArray<std::function<void (void)>> * list)65static 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: SkThreadPool(int threads)75 explicit SkThreadPool(int threads) { 76 for (int i = 0; i < threads; i++) { 77 fThreads.emplace_back(&Loop, this); 78 } 79 } 80 ~SkThreadPool()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 add(std::function<void (void)> work)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 borrow()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. do_work()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 Loop(void * ctx)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 MakeFIFOThreadPool(int threads)143std::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 } MakeLIFOThreadPool(int threads)147std::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