Line data Source code
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 295 : constexpr SkBaseSemaphore(int count = 0)
18 295 : : 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 0 : class SkSemaphore : public SkBaseSemaphore {
55 : public:
56 : using SkBaseSemaphore::SkBaseSemaphore;
57 0 : ~SkSemaphore() { this->cleanup(); }
58 : };
59 :
60 32 : inline void SkBaseSemaphore::signal(int n) {
61 64 : 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 32 : int toSignal = SkTMin(-prev, n);
73 32 : if (toSignal > 0) {
74 0 : this->osSignal(toSignal);
75 : }
76 32 : }
77 :
78 32 : 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 64 : if (fCount.fetch_sub(1, std::memory_order_acquire) <= 0) {
82 0 : this->osWait();
83 : }
84 32 : }
85 :
86 : #endif//SkSemaphore_DEFINED
|