1 /*
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
11 #include "../private/SkOnce.h"
12 #include "SkTypes.h"
13 #include <atomic>
14
15 class SkBaseSemaphore {
16 public:
17 constexpr SkBaseSemaphore(int count = 0)
fCount(count)18 : fCount(count), fOSSemaphore(nullptr) {}
19
20 // 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);
23
24 // Decrement the counter by 1,
25 // then if the counter is < 0, sleep this thread until the counter is >= 0.
26 void wait();
27
28 // If the counter is positive, decrement it by 1 and return true, otherwise return false.
29 bool try_wait();
30
31 // SkBaseSemaphore has no destructor. Call this to clean it up.
32 void cleanup();
33
34 private:
35 // 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:
43 // moving the count from >=0 to <0 or vice-versa, i.e. sleeping or waking threads.
44 struct OSSemaphore;
45
46 void osSignal(int n);
47 void osWait();
48
49 std::atomic<int> fCount;
50 SkOnce fOSSemaphoreOnce;
51 OSSemaphore* fOSSemaphore;
52 };
53
54 class SkSemaphore : public SkBaseSemaphore {
55 public:
56 using SkBaseSemaphore::SkBaseSemaphore;
~SkSemaphore()57 ~SkSemaphore() { this->cleanup(); }
58 };
59
signal(int n)60 inline 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
64 // from <0 to >=0 (when we need to wake sleeping threads).
65 //
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
wait()78 inline 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
86 #endif//SkSemaphore_DEFINED
87