OLD | NEW |
---|---|
(Empty) | |
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 SkSharedLock_DEFINED | |
9 #define SkSharedLock_DEFINED | |
10 | |
11 #include "SkAtomics.h" | |
12 #include "SkSemaphore.h" | |
13 #include "SkTypes.h" | |
14 | |
15 // This is a shared lock implementation similar to pthreads rwlocks. This implem entation is | |
16 // cribbed from Preshing's article: | |
17 // http://preshing.com/20150316/semaphores-are-surprisingly-versatile/ | |
18 // | |
19 // This lock does not obey strict queue ordering. It will always alternate betwe en readers and | |
20 // a single writer. | |
21 class SkSharedLock { | |
mtklein
2015/06/29 16:29:22
Let's implement this class in SkSharedLock.cpp and
herb_g
2015/06/29 19:26:31
Done.
| |
22 public: | |
23 SkSharedLock() | |
24 : fQueueCounts(0) { } | |
25 | |
26 // Acquire lock for exclusive use. | |
27 void acquire() { | |
28 // Increment the count of exclusive queue waiters. | |
29 int32_t oldQueueCounts = fQueueCounts.fetch_add(1 << kWaitingExlusiveOff set, | |
30 sk_memory_order_release) ; | |
mtklein
2015/06/29 16:29:22
preshing sez: sk_memory_order_acquire
herb_g
2015/06/29 19:26:31
Right. release makes no sense because the code is
| |
31 | |
32 // If there are no other exclusive waiters and no shared threads are run ning then run | |
33 // else wait. | |
34 if ((oldQueueCounts & kWaitingExclusiveMask) > 0 || (oldQueueCounts & kS haredMask) > 0) { | |
35 fExclusiveQueue.wait(); | |
36 } | |
37 } | |
38 | |
39 // Release lock for exclusive use. | |
40 void release() { | |
41 int32_t oldQueueCounts = fQueueCounts.load(sk_memory_order_relaxed); | |
42 int32_t waitingShared; | |
43 int32_t newQueueCounts; | |
44 do { | |
45 newQueueCounts = oldQueueCounts; | |
46 | |
47 // Decrement exclusive waiters. | |
48 newQueueCounts -= 1 << kWaitingExlusiveOffset; | |
49 | |
50 // The number of shared lock holders waiting. | |
mtklein
2015/06/29 16:29:22
Number of threads waiting to hold a shared lock?
herb_g
2015/06/29 19:26:31
Done.
| |
51 waitingShared = (oldQueueCounts & kWaitingSharedMask) >> kWaitingSha redOffset; | |
52 | |
53 // If there are any move the counts of all the shared waiters to act ual shared. They are | |
54 // going to run next. | |
55 if (waitingShared > 0) { | |
56 | |
57 // Set waiting shared to zero. | |
58 newQueueCounts &= ~kWaitingSharedMask; | |
59 | |
60 // Because we are waiting for writers then readers bits are zero . An or will slam | |
mtklein
2015/06/29 16:29:22
Help me through this? Is this what you're saying?
herb_g
2015/06/29 19:26:31
Done.
| |
61 // the count into the field. | |
62 newQueueCounts |= waitingShared << kSharedOffset; | |
63 } | |
64 | |
65 } while (!fQueueCounts.compare_exchange(&oldQueueCounts, newQueueCounts, | |
66 sk_memory_order_release, sk_memo ry_order_relaxed)); | |
67 | |
68 | |
69 if (waitingShared > 0) { | |
70 // Run all the shared. | |
71 fSharedQueue.signal(waitingShared); | |
72 } else if ((newQueueCounts & kWaitingExclusiveMask) > 0) { | |
73 // Run a single exclusive waiter. | |
74 fExclusiveQueue.signal(); | |
75 } | |
76 } | |
77 | |
78 // Acquire lock for shared use. | |
79 void acquireShared() { | |
80 int32_t oldQueueCounts = fQueueCounts.load(sk_memory_order_relaxed); | |
81 int32_t newQueueCounts; | |
82 do { | |
83 newQueueCounts = oldQueueCounts; | |
84 // If there are waiting exclusives then this shared lock waits else it runs. | |
85 if ((newQueueCounts & kWaitingExclusiveMask) > 0) { | |
86 newQueueCounts += 1 << kWaitingSharedOffset; | |
87 } else { | |
88 newQueueCounts += 1 << kSharedOffset; | |
89 } | |
90 } while (!fQueueCounts.compare_exchange(&oldQueueCounts, newQueueCounts, | |
91 sk_memory_order_release, sk_memo ry_order_relaxed)); | |
mtklein
2015/06/29 16:29:22
preshing sez: _acquire, _relaxed
herb_g
2015/06/29 19:26:31
Done.
| |
92 | |
93 // If there are waiting exclusives, then this shared waits until after i t runs. | |
94 if ((newQueueCounts & kWaitingExclusiveMask) > 0) { | |
95 fSharedQueue.wait(); | |
96 } | |
97 } | |
98 | |
99 // Release lock for shared use. | |
100 void releaseShared() { | |
101 // Decrement the shared count. | |
102 int32_t oldQueueCounts = fQueueCounts.fetch_add(-1 << kSharedOffset, | |
103 sk_memory_order_release) ; | |
104 | |
105 // If shared count is going to zero (because the old count == 1) and the re are exclusive | |
106 // waiters, then run a single exclusive waiter. | |
107 if (((oldQueueCounts & kSharedMask) >> kSharedOffset) == 1 | |
108 && (oldQueueCounts & kWaitingExclusiveMask) > 0) { | |
109 fExclusiveQueue.signal(); | |
110 } | |
111 } | |
112 | |
113 private: | |
114 static const int kLogThreadCount = 10; | |
mtklein
2015/06/29 16:29:22
Let's explain this? Maybe
// We assume <1024 thr
herb_g
2015/06/29 19:26:31
Done.
| |
115 // current shared: 0 - 9; | |
116 // waiting exclusive: 10 - 19; | |
117 // waiting shared: 20 - 29; | |
118 enum { | |
119 kSharedOffset = (0 * kLogThreadCount), | |
mtklein
2015/06/29 16:29:22
I personally find it a bit more readable to line u
herb_g
2015/06/29 19:26:31
Done.
| |
120 kWaitingExlusiveOffset = (1 * kLogThreadCount), | |
121 kWaitingSharedOffset = (2 * kLogThreadCount), | |
122 kSharedMask = ((1 << kLogThreadCount) - 1) << kSharedOffset, | |
123 kWaitingExclusiveMask = ((1 << kLogThreadCount) - 1) << kWaitingExlusive Offset, | |
124 kWaitingSharedMask = ((1 << kLogThreadCount) - 1) << kWaitingSharedOffse t, | |
125 }; | |
126 SkAtomic<int32_t> fQueueCounts; | |
mtklein
2015/06/29 16:29:22
// Using an int32_t makes it easier to call .fetch
herb_g
2015/06/29 19:26:31
Acknowledged.
| |
127 SkSemaphore fSharedQueue; | |
128 SkSemaphore fExclusiveQueue; | |
129 }; | |
130 | |
131 #endif // SkSharedLock_DEFINED | |
OLD | NEW |