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