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