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