/* * Copyright 2017 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkExecutor.h" #include "include/private/SkMutex.h" #include "include/private/SkSemaphore.h" #include "include/private/SkSpinlock.h" #include "include/private/SkTArray.h" #include "src/core/SkMakeUnique.h" #include #include #if defined(SK_BUILD_FOR_WIN) #include "src/core/SkLeanWindows.h" static int num_cores() { SYSTEM_INFO sysinfo; GetNativeSystemInfo(&sysinfo); return (int)sysinfo.dwNumberOfProcessors; } #else #include static int num_cores() { return (int)sysconf(_SC_NPROCESSORS_ONLN); } #endif SkExecutor::~SkExecutor() {} // The default default SkExecutor is an SkTrivialExecutor, which just runs the work right away. class SkTrivialExecutor final : public SkExecutor { void add(std::function work) override { work(); } }; static SkExecutor* gDefaultExecutor = nullptr; void SetDefaultTrivialExecutor() { static SkTrivialExecutor *gTrivial = new SkTrivialExecutor(); gDefaultExecutor = gTrivial; } SkExecutor& SkExecutor::GetDefault() { if (!gDefaultExecutor) { SetDefaultTrivialExecutor(); } return *gDefaultExecutor; } void SkExecutor::SetDefault(SkExecutor* executor) { if (executor) { gDefaultExecutor = executor; } else { SetDefaultTrivialExecutor(); } } // We'll always push_back() new work, but pop from the front of deques or the back of SkTArray. static inline std::function pop(std::deque>* list) { std::function fn = std::move(list->front()); list->pop_front(); return fn; } static inline std::function pop(SkTArray>* list) { std::function fn = std::move(list->back()); list->pop_back(); return fn; } // An SkThreadPool is an executor that runs work on a fixed pool of OS threads. template class SkThreadPool final : public SkExecutor { public: explicit SkThreadPool(int threads) { for (int i = 0; i < threads; i++) { fThreads.emplace_back(&Loop, this); } } ~SkThreadPool() override { // Signal each thread that it's time to shut down. for (int i = 0; i < fThreads.count(); i++) { this->add(nullptr); } // Wait for each thread to shut down. for (int i = 0; i < fThreads.count(); i++) { fThreads[i].join(); } } virtual void add(std::function work) override { // Add some work to our pile of work to do. { SkAutoMutexExclusive lock(fWorkLock); fWork.emplace_back(std::move(work)); } // Tell the Loop() threads to pick it up. fWorkAvailable.signal(1); } virtual void borrow() override { // If there is work waiting, do it. if (fWorkAvailable.try_wait()) { SkAssertResult(this->do_work()); } } private: // This method should be called only when fWorkAvailable indicates there's work to do. bool do_work() { std::function work; { SkAutoMutexExclusive lock(fWorkLock); SkASSERT(!fWork.empty()); // TODO: if (fWork.empty()) { return true; } ? work = pop(&fWork); } if (!work) { return false; // This is Loop()'s signal to shut down. } work(); return true; } static void Loop(void* ctx) { auto pool = (SkThreadPool*)ctx; do { pool->fWorkAvailable.wait(); } while (pool->do_work()); } // Both SkMutex and SkSpinlock can work here. using Lock = SkMutex; SkTArray fThreads; WorkList fWork; Lock fWorkLock; SkSemaphore fWorkAvailable; }; std::unique_ptr SkExecutor::MakeFIFOThreadPool(int threads) { using WorkList = std::deque>; return skstd::make_unique>(threads > 0 ? threads : num_cores()); } std::unique_ptr SkExecutor::MakeLIFOThreadPool(int threads) { using WorkList = SkTArray>; return skstd::make_unique>(threads > 0 ? threads : num_cores()); }