public void ShouldRunThreadsWithDistinctKeysInParallel() { // Arrange var currentParallelism = 0; var maxParallelism = 0; var parallelismLock = new object(); // 100 threads, 100 keys var threads = Enumerable.Range(0, 100) .Select(i => new Thread(() => OccupyTheLockALittleBit(i))) .ToList(); // Act foreach (var thread in threads) { thread.Start(); } foreach (var thread in threads) { thread.Join(); } maxParallelism.Should().BeGreaterThan(10); void OccupyTheLockALittleBit(int key) { using (KeyedSemaphore.Lock(key.ToString())) { var incrementedCurrentParallelism = Interlocked.Increment(ref currentParallelism); lock (parallelismLock) { maxParallelism = Math.Max(incrementedCurrentParallelism, maxParallelism); } const int delay = 250; Thread.Sleep(TimeSpan.FromMilliseconds(delay)); Interlocked.Decrement(ref currentParallelism); } } }
public void ShouldRunThreadsWithSameKeysLinearly() { // Arrange var runningThreadsIndex = new ConcurrentDictionary <int, int>(); var parallelismLock = new object(); var currentParallelism = 0; var maxParallelism = 0; // 100 threads, 10 keys var threads = Enumerable.Range(0, 100) .Select(i => new Thread(() => OccupyTheLockALittleBit(i % 10))) .ToList(); // Act foreach (var thread in threads) { thread.Start(); } foreach (var thread in threads) { thread.Join(); } // Assert maxParallelism.Should().BeLessOrEqualTo(10); void OccupyTheLockALittleBit(int key) { using (KeyedSemaphore.Lock(key.ToString())) { var incrementedCurrentParallelism = Interlocked.Increment(ref currentParallelism); lock (parallelismLock) { maxParallelism = Math.Max(incrementedCurrentParallelism, maxParallelism); } var currentThreadId = Thread.CurrentThread.ManagedThreadId; if (runningThreadsIndex.TryGetValue(key, out var otherThread)) { throw new Exception($"Thread #{currentThreadId} acquired a lock using key ${key} " + $"but another thread #{otherThread} is also still running using this key!"); } runningThreadsIndex[key] = currentThreadId; const int delay = 10; Thread.Sleep(TimeSpan.FromMilliseconds(delay)); if (!runningThreadsIndex.TryRemove(key, out var value)) { var ex = new Exception($"Thread #{currentThreadId} has finished " + $"but when trying to cleanup the running threads index, the value is already gone"); throw ex; } if (value != currentThreadId) { var ex = new Exception($"Thread #{currentThreadId} has finished and has removed itself from the running threads index," + $" but that index contained an incorrect value: #{value}!"); throw ex; } Interlocked.Decrement(ref currentParallelism); } } }