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