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