OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2015 Google Inc. | 2 * Copyright 2015 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #ifndef SkSemaphore_DEFINED | 8 #ifndef SkSemaphore_DEFINED |
9 #define SkSemaphore_DEFINED | 9 #define SkSemaphore_DEFINED |
10 | 10 |
| 11 #include "../private/SkOnce.h" |
11 #include "SkTypes.h" | 12 #include "SkTypes.h" |
12 #include "../private/SkAtomics.h" | 13 #include <atomic> |
13 #include "../private/SkOncePtr.h" | |
14 | 14 |
15 struct SkBaseSemaphore { | 15 class SkBaseSemaphore { |
| 16 public: |
| 17 constexpr SkBaseSemaphore(int count = 0) |
| 18 : fCount(count), fOSSemaphore(nullptr) {} |
16 | 19 |
17 // Increment the counter by 1. | 20 // Increment the counter n times. |
18 // This is a specialization for supporting SkMutex. | 21 // Generally it's better to call signal(n) instead of signal() n times. |
19 void signal() { | 22 void signal(int n = 1); |
20 // Since this fetches the value before the add, 0 indicates that this th
read is running and | |
21 // no threads are waiting, -1 and below means that threads are waiting,
but only signal 1 | |
22 // thread to run. | |
23 if (sk_atomic_fetch_add(&fCount, 1, sk_memory_order_release) < 0) { | |
24 this->osSignal(1); | |
25 } | |
26 } | |
27 | |
28 // Increment the counter N times. | |
29 // Generally it's better to call signal(N) instead of signal() N times. | |
30 void signal(int N); | |
31 | 23 |
32 // Decrement the counter by 1, | 24 // Decrement the counter by 1, |
33 // then if the counter is <= 0, sleep this thread until the counter is > 0. | 25 // then if the counter is <= 0, sleep this thread until the counter is > 0. |
34 void wait() { | 26 void wait(); |
35 // Since this fetches the value before the subtract, zero and below mean
s that there are no | |
36 // resources left, so the thread needs to wait. | |
37 if (sk_atomic_fetch_sub(&fCount, 1, sk_memory_order_acquire) <= 0) { | |
38 this->osWait(); | |
39 } | |
40 } | |
41 | 27 |
42 struct OSSemaphore; | 28 // SkBaseSemaphore has no destructor. Call this to clean it up. |
| 29 void cleanup(); |
43 | 30 |
44 void osSignal(int n); | 31 private: |
45 void osWait(); | |
46 void deleteSemaphore(); | |
47 | |
48 // This implementation follows the general strategy of | 32 // This implementation follows the general strategy of |
49 // 'A Lightweight Semaphore with Partial Spinning' | 33 // 'A Lightweight Semaphore with Partial Spinning' |
50 // found here | 34 // found here |
51 // http://preshing.com/20150316/semaphores-are-surprisingly-versatile/ | 35 // http://preshing.com/20150316/semaphores-are-surprisingly-versatile/ |
52 // That article (and entire blog) are very much worth reading. | 36 // That article (and entire blog) are very much worth reading. |
53 // | 37 // |
54 // We wrap an OS-provided semaphore with a user-space atomic counter that | 38 // We wrap an OS-provided semaphore with a user-space atomic counter that |
55 // lets us avoid interacting with the OS semaphore unless strictly required: | 39 // lets us avoid interacting with the OS semaphore unless strictly required: |
56 // moving the count from >0 to <=0 or vice-versa, i.e. sleeping or waking th
reads. | 40 // moving the count from >0 to <=0 or vice-versa, i.e. sleeping or waking th
reads. |
57 int fCount; | 41 struct OSSemaphore; |
58 SkBaseOncePtr<OSSemaphore> fOSSemaphore; | 42 |
| 43 void osSignal(int n); |
| 44 void osWait(); |
| 45 |
| 46 std::atomic<int> fCount; |
| 47 SkOnce fOSSemaphoreOnce; |
| 48 OSSemaphore* fOSSemaphore; |
59 }; | 49 }; |
60 | 50 |
61 /** | 51 class SkSemaphore : public SkBaseSemaphore { |
62 * SkSemaphore is a fast mostly-user-space semaphore. | |
63 * | |
64 * A semaphore is logically an atomic integer with a few special properties: | |
65 * - The integer always starts at 0. | |
66 * - You can only increment or decrement it, never read or write it. | |
67 * - Increment is spelled 'signal()'; decrement is spelled 'wait()'. | |
68 * - If a call to wait() decrements the counter to <= 0, | |
69 * the calling thread sleeps until another thread signal()s it back above 0. | |
70 */ | |
71 class SkSemaphore : SkNoncopyable { | |
72 public: | 52 public: |
73 // Initializes the counter to 0. | 53 using SkBaseSemaphore::SkBaseSemaphore; |
74 // (Though all current implementations could start from an arbitrary value.) | 54 ~SkSemaphore() { this->cleanup(); } |
75 SkSemaphore(); | |
76 ~SkSemaphore(); | |
77 | |
78 void wait(); | |
79 | |
80 void signal(int n = 1); | |
81 | |
82 private: | |
83 SkBaseSemaphore fBaseSemaphore; | |
84 }; | 55 }; |
85 | 56 |
| 57 inline void SkBaseSemaphore::signal(int n) { |
| 58 int prev = fCount.fetch_add(n, std::memory_order_release); |
| 59 |
| 60 // We only want to call the OS semaphore when our logical count crosses |
| 61 // from <= 0 to >0 (when we need to wake sleeping threads). |
| 62 // |
| 63 // This is easiest to think about with specific examples of prev and n. |
| 64 // If n == 5 and prev == -3, there are 3 threads sleeping and we signal |
| 65 // SkTMin(-(-3), 5) == 3 times on the OS semaphore, leaving the count at 2. |
| 66 // |
| 67 // If prev >= 0, no threads are waiting, SkTMin(-prev, n) is always <= 0, |
| 68 // so we don't call the OS semaphore, leaving the count at (prev + n). |
| 69 int toSignal = SkTMin(-prev, n); |
| 70 if (toSignal > 0) { |
| 71 this->osSignal(toSignal); |
| 72 } |
| 73 } |
| 74 |
| 75 inline void SkBaseSemaphore::wait() { |
| 76 // Since this fetches the value before the subtract, zero and below means th
at there are no |
| 77 // resources left, so the thread needs to wait. |
| 78 if (fCount.fetch_sub(1, std::memory_order_acquire) <= 0) { |
| 79 this->osWait(); |
| 80 } |
| 81 } |
| 82 |
86 #endif//SkSemaphore_DEFINED | 83 #endif//SkSemaphore_DEFINED |
OLD | NEW |