blob: aeca9729dc87ba6b786e6f432bc0982da55ce8a0 [file] [log] [blame]
mtklein61fa22b2015-06-17 10:50:25 -07001/*
2 * Copyright 2015 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#ifndef SkSemaphore_DEFINED
9#define SkSemaphore_DEFINED
10
mtklein42846ed2016-05-05 10:57:37 -070011#include "../private/SkOnce.h"
mtklein61fa22b2015-06-17 10:50:25 -070012#include "SkTypes.h"
mtklein42846ed2016-05-05 10:57:37 -070013#include <atomic>
herb7f0a3d72015-09-24 07:34:49 -070014
mtklein42846ed2016-05-05 10:57:37 -070015class SkBaseSemaphore {
16public:
17 constexpr SkBaseSemaphore(int count = 0)
18 : fCount(count), fOSSemaphore(nullptr) {}
herb7f0a3d72015-09-24 07:34:49 -070019
mtklein42846ed2016-05-05 10:57:37 -070020 // Increment the counter n times.
21 // Generally it's better to call signal(n) instead of signal() n times.
22 void signal(int n = 1);
herb7f0a3d72015-09-24 07:34:49 -070023
24 // Decrement the counter by 1,
Mike Klein19eec392017-02-22 14:33:12 -050025 // then if the counter is < 0, sleep this thread until the counter is >= 0.
mtklein42846ed2016-05-05 10:57:37 -070026 void wait();
herb7f0a3d72015-09-24 07:34:49 -070027
Mike Klein384b90a2017-02-21 22:53:16 -050028 // If the counter is positive, decrement it by 1 and return true, otherwise return false.
29 bool try_wait();
30
mtklein42846ed2016-05-05 10:57:37 -070031 // SkBaseSemaphore has no destructor. Call this to clean it up.
32 void cleanup();
sclittled9f5d202016-05-04 18:23:30 -070033
mtklein42846ed2016-05-05 10:57:37 -070034private:
herb7f0a3d72015-09-24 07:34:49 -070035 // This implementation follows the general strategy of
36 // 'A Lightweight Semaphore with Partial Spinning'
37 // found here
38 // http://preshing.com/20150316/semaphores-are-surprisingly-versatile/
39 // That article (and entire blog) are very much worth reading.
40 //
41 // We wrap an OS-provided semaphore with a user-space atomic counter that
42 // lets us avoid interacting with the OS semaphore unless strictly required:
Mike Klein19eec392017-02-22 14:33:12 -050043 // moving the count from >=0 to <0 or vice-versa, i.e. sleeping or waking threads.
mtklein42846ed2016-05-05 10:57:37 -070044 struct OSSemaphore;
45
46 void osSignal(int n);
47 void osWait();
48
49 std::atomic<int> fCount;
50 SkOnce fOSSemaphoreOnce;
51 OSSemaphore* fOSSemaphore;
herb7f0a3d72015-09-24 07:34:49 -070052};
mtklein61fa22b2015-06-17 10:50:25 -070053
mtklein42846ed2016-05-05 10:57:37 -070054class SkSemaphore : public SkBaseSemaphore {
sclittled9f5d202016-05-04 18:23:30 -070055public:
mtklein42846ed2016-05-05 10:57:37 -070056 using SkBaseSemaphore::SkBaseSemaphore;
57 ~SkSemaphore() { this->cleanup(); }
sclittled9f5d202016-05-04 18:23:30 -070058};
mtklein61fa22b2015-06-17 10:50:25 -070059
mtklein42846ed2016-05-05 10:57:37 -070060inline void SkBaseSemaphore::signal(int n) {
61 int prev = fCount.fetch_add(n, std::memory_order_release);
62
63 // We only want to call the OS semaphore when our logical count crosses
Mike Klein19eec392017-02-22 14:33:12 -050064 // from <0 to >=0 (when we need to wake sleeping threads).
mtklein42846ed2016-05-05 10:57:37 -070065 //
66 // This is easiest to think about with specific examples of prev and n.
67 // If n == 5 and prev == -3, there are 3 threads sleeping and we signal
68 // SkTMin(-(-3), 5) == 3 times on the OS semaphore, leaving the count at 2.
69 //
70 // If prev >= 0, no threads are waiting, SkTMin(-prev, n) is always <= 0,
71 // so we don't call the OS semaphore, leaving the count at (prev + n).
72 int toSignal = SkTMin(-prev, n);
73 if (toSignal > 0) {
74 this->osSignal(toSignal);
75 }
76}
77
78inline void SkBaseSemaphore::wait() {
79 // Since this fetches the value before the subtract, zero and below means that there are no
80 // resources left, so the thread needs to wait.
81 if (fCount.fetch_sub(1, std::memory_order_acquire) <= 0) {
82 this->osWait();
83 }
84}
85
mtklein61fa22b2015-06-17 10:50:25 -070086#endif//SkSemaphore_DEFINED