This is the seventh part of the Concurrency series. For your convenience you can find other parts in the table of contents in Part 1 – Mutex performance in .NET
Last time we examined an interesting behavior of Mutex
when it is abandoned. Today we will look into Semaphore
.
Typical interview question is: what is the difference between a binary mutex and a semaphore? Exemplary answer is: mutex tracks owner, semaphore doesn’t. This is not always true, not all mutexes do that (ones that do are called recursive mutexes). However, based on this definition we can easily see a problem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using System; using System.Threading; namespace AbandonedSemaphore { class Program { static void Main(string[] args) { var semaphore = new Semaphore(2, 2, "semaphoreName"); semaphore.WaitOne(); Console.WriteLine("Acquired!"); Console.ReadLine(); semaphore.Release(); Console.WriteLine("Done"); } } } |
Run three instances and then kill first two. Last instance will never get the semaphore. Why? Because semaphore doesn’t track the owner so it cannot throw any exception when process dies.
Can we make it better? Yes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
using System; using System.Linq; using System.Threading; namespace AbandonedSemaphoreBetter { class Program { static void Main(string[] args) { var mutexes = Enumerable.Range(0, 2).Select(i => new Mutex(false, "mutexName" + i)).ToArray(); int id = -1; try { id = WaitHandle.WaitAny(mutexes); } catch(AbandonedMutexException e) { id = e.MutexIndex; } Console.WriteLine("Acquired"); Console.ReadLine(); mutexes[id].ReleaseMutex(); Console.WriteLine("Released"); } } } |
We have an array of mutexes and track which one was acquired. Notice that id is also available in the exception so we know which one was freed by killing the process.