@@ -11,13 +11,29 @@ namespace System.Threading
11
11
/// A LIFO semaphore.
12
12
/// Waits on this semaphore are uninterruptible.
13
13
/// </summary>
14
- internal sealed partial class LowLevelLifoSemaphore : LowLevelLifoSemaphoreBase , IDisposable
14
+ internal sealed partial class LowLevelLifoSemaphore : IDisposable
15
15
{
16
+ private CacheLineSeparatedCounts _separated ;
17
+
18
+ private readonly int _maximumSignalCount ;
19
+ private readonly int _spinCount ;
20
+ private readonly Action _onWait ;
21
+
16
22
private const int SpinSleep0Threshold = 10 ;
17
23
18
24
public LowLevelLifoSemaphore ( int initialSignalCount , int maximumSignalCount , int spinCount , Action onWait )
19
- : base ( initialSignalCount , maximumSignalCount , spinCount , onWait )
20
25
{
26
+ Debug . Assert ( initialSignalCount >= 0 ) ;
27
+ Debug . Assert ( initialSignalCount <= maximumSignalCount ) ;
28
+ Debug . Assert ( maximumSignalCount > 0 ) ;
29
+ Debug . Assert ( spinCount >= 0 ) ;
30
+
31
+ _separated = default ;
32
+ _separated . _counts . SignalCount = ( uint ) initialSignalCount ;
33
+ _maximumSignalCount = maximumSignalCount ;
34
+ _spinCount = spinCount ;
35
+ _onWait = onWait ;
36
+
21
37
Create ( maximumSignalCount ) ;
22
38
}
23
39
@@ -185,5 +201,178 @@ private bool WaitForSignal(int timeoutMs)
185
201
}
186
202
}
187
203
}
204
+
205
+ public void Release ( int releaseCount )
206
+ {
207
+ Debug . Assert ( releaseCount > 0 ) ;
208
+ Debug . Assert ( releaseCount <= _maximumSignalCount ) ;
209
+
210
+ int countOfWaitersToWake ;
211
+ Counts counts = _separated . _counts ;
212
+ while ( true )
213
+ {
214
+ Counts newCounts = counts ;
215
+
216
+ // Increase the signal count. The addition doesn't overflow because of the limit on the max signal count in constructor.
217
+ newCounts . AddSignalCount ( ( uint ) releaseCount ) ;
218
+
219
+ // Determine how many waiters to wake, taking into account how many spinners and waiters there are and how many waiters
220
+ // have previously been signaled to wake but have not yet woken
221
+ countOfWaitersToWake =
222
+ ( int ) Math . Min ( newCounts . SignalCount , ( uint ) counts . WaiterCount + counts . SpinnerCount ) -
223
+ counts . SpinnerCount -
224
+ counts . CountOfWaitersSignaledToWake ;
225
+ if ( countOfWaitersToWake > 0 )
226
+ {
227
+ // Ideally, limiting to a maximum of releaseCount would not be necessary and could be an assert instead, but since
228
+ // WaitForSignal() does not have enough information to tell whether a woken thread was signaled, and due to the cap
229
+ // below, it's possible for countOfWaitersSignaledToWake to be less than the number of threads that have actually
230
+ // been signaled to wake.
231
+ if ( countOfWaitersToWake > releaseCount )
232
+ {
233
+ countOfWaitersToWake = releaseCount ;
234
+ }
235
+
236
+ // Cap countOfWaitersSignaledToWake to its max value. It's ok to ignore some woken threads in this count, it just
237
+ // means some more threads will be woken next time. Typically, it won't reach the max anyway.
238
+ newCounts . AddUpToMaxCountOfWaitersSignaledToWake ( ( uint ) countOfWaitersToWake ) ;
239
+ }
240
+
241
+ Counts countsBeforeUpdate = _separated . _counts . InterlockedCompareExchange ( newCounts , counts ) ;
242
+ if ( countsBeforeUpdate == counts )
243
+ {
244
+ Debug . Assert ( releaseCount <= _maximumSignalCount - counts . SignalCount ) ;
245
+ if ( countOfWaitersToWake > 0 )
246
+ ReleaseCore ( countOfWaitersToWake ) ;
247
+ return ;
248
+ }
249
+
250
+ counts = countsBeforeUpdate ;
251
+ }
252
+ }
253
+
254
+ private struct Counts : IEquatable < Counts >
255
+ {
256
+ private const byte SignalCountShift = 0 ;
257
+ private const byte WaiterCountShift = 32 ;
258
+ private const byte SpinnerCountShift = 48 ;
259
+ private const byte CountOfWaitersSignaledToWakeShift = 56 ;
260
+
261
+ private ulong _data ;
262
+
263
+ private Counts ( ulong data ) => _data = data ;
264
+
265
+ private uint GetUInt32Value ( byte shift ) => ( uint ) ( _data >> shift ) ;
266
+ private void SetUInt32Value ( uint value , byte shift ) =>
267
+ _data = ( _data & ~ ( ( ulong ) uint . MaxValue << shift ) ) | ( ( ulong ) value << shift ) ;
268
+ private ushort GetUInt16Value ( byte shift ) => ( ushort ) ( _data >> shift ) ;
269
+ private void SetUInt16Value ( ushort value , byte shift ) =>
270
+ _data = ( _data & ~ ( ( ulong ) ushort . MaxValue << shift ) ) | ( ( ulong ) value << shift ) ;
271
+ private byte GetByteValue ( byte shift ) => ( byte ) ( _data >> shift ) ;
272
+ private void SetByteValue ( byte value , byte shift ) =>
273
+ _data = ( _data & ~ ( ( ulong ) byte . MaxValue << shift ) ) | ( ( ulong ) value << shift ) ;
274
+
275
+ public uint SignalCount
276
+ {
277
+ get => GetUInt32Value ( SignalCountShift ) ;
278
+ set => SetUInt32Value ( value , SignalCountShift ) ;
279
+ }
280
+
281
+ public void AddSignalCount ( uint value )
282
+ {
283
+ Debug . Assert ( value <= uint . MaxValue - SignalCount ) ;
284
+ _data += ( ulong ) value << SignalCountShift ;
285
+ }
286
+
287
+ public void IncrementSignalCount ( ) => AddSignalCount ( 1 ) ;
288
+
289
+ public void DecrementSignalCount ( )
290
+ {
291
+ Debug . Assert ( SignalCount != 0 ) ;
292
+ _data -= ( ulong ) 1 << SignalCountShift ;
293
+ }
294
+
295
+ public ushort WaiterCount
296
+ {
297
+ get => GetUInt16Value ( WaiterCountShift ) ;
298
+ set => SetUInt16Value ( value , WaiterCountShift ) ;
299
+ }
300
+
301
+ public void IncrementWaiterCount ( )
302
+ {
303
+ Debug . Assert ( WaiterCount < ushort . MaxValue ) ;
304
+ _data += ( ulong ) 1 << WaiterCountShift ;
305
+ }
306
+
307
+ public void DecrementWaiterCount ( )
308
+ {
309
+ Debug . Assert ( WaiterCount != 0 ) ;
310
+ _data -= ( ulong ) 1 << WaiterCountShift ;
311
+ }
312
+
313
+ public void InterlockedDecrementWaiterCount ( )
314
+ {
315
+ var countsAfterUpdate = new Counts ( Interlocked . Add ( ref _data , unchecked ( ( ulong ) - 1 ) << WaiterCountShift ) ) ;
316
+ Debug . Assert ( countsAfterUpdate . WaiterCount != ushort . MaxValue ) ; // underflow check
317
+ }
318
+
319
+ public byte SpinnerCount
320
+ {
321
+ get => GetByteValue ( SpinnerCountShift ) ;
322
+ set => SetByteValue ( value , SpinnerCountShift ) ;
323
+ }
324
+
325
+ public void IncrementSpinnerCount ( )
326
+ {
327
+ Debug . Assert ( SpinnerCount < byte . MaxValue ) ;
328
+ _data += ( ulong ) 1 << SpinnerCountShift ;
329
+ }
330
+
331
+ public void DecrementSpinnerCount ( )
332
+ {
333
+ Debug . Assert ( SpinnerCount != 0 ) ;
334
+ _data -= ( ulong ) 1 << SpinnerCountShift ;
335
+ }
336
+
337
+ public byte CountOfWaitersSignaledToWake
338
+ {
339
+ get => GetByteValue ( CountOfWaitersSignaledToWakeShift ) ;
340
+ set => SetByteValue ( value , CountOfWaitersSignaledToWakeShift ) ;
341
+ }
342
+
343
+ public void AddUpToMaxCountOfWaitersSignaledToWake ( uint value )
344
+ {
345
+ uint availableCount = ( uint ) ( byte . MaxValue - CountOfWaitersSignaledToWake ) ;
346
+ if ( value > availableCount )
347
+ {
348
+ value = availableCount ;
349
+ }
350
+ _data += ( ulong ) value << CountOfWaitersSignaledToWakeShift ;
351
+ }
352
+
353
+ public void DecrementCountOfWaitersSignaledToWake ( )
354
+ {
355
+ Debug . Assert ( CountOfWaitersSignaledToWake != 0 ) ;
356
+ _data -= ( ulong ) 1 << CountOfWaitersSignaledToWakeShift ;
357
+ }
358
+
359
+ public Counts InterlockedCompareExchange ( Counts newCounts , Counts oldCounts ) =>
360
+ new Counts ( Interlocked . CompareExchange ( ref _data , newCounts . _data , oldCounts . _data ) ) ;
361
+
362
+ public static bool operator == ( Counts lhs , Counts rhs ) => lhs . Equals ( rhs ) ;
363
+ public static bool operator != ( Counts lhs , Counts rhs ) => ! lhs . Equals ( rhs ) ;
364
+
365
+ public override bool Equals ( [ NotNullWhen ( true ) ] object ? obj ) => obj is Counts other && Equals ( other ) ;
366
+ public bool Equals ( Counts other ) => _data == other . _data ;
367
+ public override int GetHashCode ( ) => ( int ) _data + ( int ) ( _data >> 32 ) ;
368
+ }
369
+
370
+ [ StructLayout ( LayoutKind . Sequential ) ]
371
+ private struct CacheLineSeparatedCounts
372
+ {
373
+ private readonly Internal . PaddingFor32 _pad1 ;
374
+ public Counts _counts ;
375
+ private readonly Internal . PaddingFor32 _pad2 ;
376
+ }
188
377
}
189
378
}
0 commit comments