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