Mike Klein | 384b90a | 2017-02-21 22:53:16 -0500 | [diff] [blame] | 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(_MSC_VER) |
| 17 | #include <windows.h> |
| 18 | static int num_cores() { |
| 19 | SYSTEM_INFO sysinfo; |
| 20 | GetNativeSystemInfo(&sysinfo); |
| 21 | return (int)sysinfo.dwNumberOfProcessors; |
| 22 | } |
| 23 | #else |
| 24 | #include <unistd.h> |
| 25 | static int num_cores() { |
| 26 | return (int)sysconf(_SC_NPROCESSORS_ONLN); |
| 27 | } |
| 28 | #endif |
| 29 | |
| 30 | SkExecutor::~SkExecutor() {} |
| 31 | |
| 32 | // The default default SkExecutor is an SkTrivialExecutor, which just runs the work right away. |
| 33 | class SkTrivialExecutor final : public SkExecutor { |
| 34 | void add(std::function<void(void)> work) override { |
| 35 | work(); |
| 36 | } |
| 37 | }; |
| 38 | |
| 39 | static SkTrivialExecutor gTrivial; |
| 40 | static SkExecutor* gDefaultExecutor = &gTrivial; |
| 41 | |
| 42 | SkExecutor& SkExecutor::GetDefault() { |
| 43 | return *gDefaultExecutor; |
| 44 | } |
| 45 | void 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: |
| 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 | |
| 59 | ~SkThreadPool() { |
| 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 | |
| 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 | |
| 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. |
| 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 | |
| 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 | |
| 122 | std::unique_ptr<SkExecutor> SkExecutor::MakeThreadPool(int threads) { |
| 123 | return skstd::make_unique<SkThreadPool>(threads > 0 ? threads : num_cores()); |
| 124 | } |