| /* |
| * 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 "SkExecutor.h" |
| #include "SkMakeUnique.h" |
| #include "SkMutex.h" |
| #include "SkSemaphore.h" |
| #include "SkSpinlock.h" |
| #include "SkTArray.h" |
| #include "SkThreadUtils.h" |
| |
| #if defined(STARBOARD) |
| #include "starboard/system.h" |
| static int num_cores() { |
| return SbSystemGetNumberOfProcessors(); |
| } |
| #else // defined(STARBOARD) |
| #if defined(SK_BUILD_FOR_WIN32) |
| #include <windows.h> |
| static int num_cores() { |
| SYSTEM_INFO sysinfo; |
| GetNativeSystemInfo(&sysinfo); |
| return (int)sysinfo.dwNumberOfProcessors; |
| } |
| #else |
| #include <unistd.h> |
| static int num_cores() { |
| return (int)sysconf(_SC_NPROCESSORS_ONLN); |
| } |
| #endif |
| #endif // defined(STARBOARD) |
| |
| 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<void(void)> work) override { |
| work(); |
| } |
| }; |
| |
| static SkTrivialExecutor gTrivial; |
| static SkExecutor* gDefaultExecutor = &gTrivial; |
| |
| SkExecutor& SkExecutor::GetDefault() { |
| return *gDefaultExecutor; |
| } |
| void SkExecutor::SetDefault(SkExecutor* executor) { |
| gDefaultExecutor = executor ? executor : &gTrivial; |
| } |
| |
| // An SkThreadPool is an executor that runs work on a fixed pool of OS threads. |
| class SkThreadPool final : public SkExecutor { |
| public: |
| explicit SkThreadPool(int threads) { |
| for (int i = 0; i < threads; i++) { |
| fThreads.emplace_back(new SkThread(&Loop, this)); |
| fThreads.back()->start(); |
| } |
| } |
| |
| ~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<void(void)> work) override { |
| // Add some work to our pile of work to do. |
| { |
| SkAutoExclusive 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<void(void)> work; |
| { |
| SkAutoExclusive lock(fWorkLock); |
| SkASSERT(!fWork.empty()); // TODO: if (fWork.empty()) { return true; } ? |
| work = std::move(fWork.back()); |
| fWork.pop_back(); |
| } |
| |
| 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<std::unique_ptr<SkThread>> fThreads; |
| SkTArray<std::function<void(void)>> fWork; |
| Lock fWorkLock; |
| SkSemaphore fWorkAvailable; |
| }; |
| |
| std::unique_ptr<SkExecutor> SkExecutor::MakeThreadPool(int threads) { |
| return skstd::make_unique<SkThreadPool>(threads > 0 ? threads : num_cores()); |
| } |