blob: 3da2b99ab4a9f6bf4dd812633f159c04ef729af5 [file] [log] [blame]
mtklein61fa22b2015-06-17 10:50:25 -07001/*
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
mtklein42846ed2016-05-05 10:57:37 -070011#include "../private/SkOnce.h"
mtklein61fa22b2015-06-17 10:50:25 -070012#include "SkTypes.h"
mtklein42846ed2016-05-05 10:57:37 -070013#include <atomic>
herb7f0a3d72015-09-24 07:34:49 -070014
mtklein42846ed2016-05-05 10:57:37 -070015class SkBaseSemaphore {
16public:
17 constexpr SkBaseSemaphore(int count = 0)
18 : fCount(count), fOSSemaphore(nullptr) {}
herb7f0a3d72015-09-24 07:34:49 -070019
mtklein42846ed2016-05-05 10:57:37 -070020 // 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);
herb7f0a3d72015-09-24 07:34:49 -070023
24 // Decrement the counter by 1,
25 // then if the counter is <= 0, sleep this thread until the counter is > 0.
mtklein42846ed2016-05-05 10:57:37 -070026 void wait();
herb7f0a3d72015-09-24 07:34:49 -070027
mtklein42846ed2016-05-05 10:57:37 -070028 // SkBaseSemaphore has no destructor. Call this to clean it up.
29 void cleanup();
sclittled9f5d202016-05-04 18:23:30 -070030
mtklein42846ed2016-05-05 10:57:37 -070031private:
herb7f0a3d72015-09-24 07:34:49 -070032 // This implementation follows the general strategy of
33 // 'A Lightweight Semaphore with Partial Spinning'
34 // found here
35 // http://preshing.com/20150316/semaphores-are-surprisingly-versatile/
36 // That article (and entire blog) are very much worth reading.
37 //
38 // We wrap an OS-provided semaphore with a user-space atomic counter that
39 // lets us avoid interacting with the OS semaphore unless strictly required:
40 // moving the count from >0 to <=0 or vice-versa, i.e. sleeping or waking threads.
mtklein42846ed2016-05-05 10:57:37 -070041 struct OSSemaphore;
42
43 void osSignal(int n);
44 void osWait();
45
46 std::atomic<int> fCount;
47 SkOnce fOSSemaphoreOnce;
48 OSSemaphore* fOSSemaphore;
herb7f0a3d72015-09-24 07:34:49 -070049};
mtklein61fa22b2015-06-17 10:50:25 -070050
mtklein42846ed2016-05-05 10:57:37 -070051class SkSemaphore : public SkBaseSemaphore {
sclittled9f5d202016-05-04 18:23:30 -070052public:
mtklein42846ed2016-05-05 10:57:37 -070053 using SkBaseSemaphore::SkBaseSemaphore;
54 ~SkSemaphore() { this->cleanup(); }
sclittled9f5d202016-05-04 18:23:30 -070055};
mtklein61fa22b2015-06-17 10:50:25 -070056
mtklein42846ed2016-05-05 10:57:37 -070057inline void SkBaseSemaphore::signal(int n) {
58 int prev = fCount.fetch_add(n, std::memory_order_release);
59
60 // We only want to call the OS semaphore when our logical count crosses
61 // from <= 0 to >0 (when we need to wake sleeping threads).
62 //
63 // This is easiest to think about with specific examples of prev and n.
64 // If n == 5 and prev == -3, there are 3 threads sleeping and we signal
65 // SkTMin(-(-3), 5) == 3 times on the OS semaphore, leaving the count at 2.
66 //
67 // If prev >= 0, no threads are waiting, SkTMin(-prev, n) is always <= 0,
68 // so we don't call the OS semaphore, leaving the count at (prev + n).
69 int toSignal = SkTMin(-prev, n);
70 if (toSignal > 0) {
71 this->osSignal(toSignal);
72 }
73}
74
75inline void SkBaseSemaphore::wait() {
76 // Since this fetches the value before the subtract, zero and below means that there are no
77 // resources left, so the thread needs to wait.
78 if (fCount.fetch_sub(1, std::memory_order_acquire) <= 0) {
79 this->osWait();
80 }
81}
82
mtklein61fa22b2015-06-17 10:50:25 -070083#endif//SkSemaphore_DEFINED