Interprocess Communication: Part 4

December 17, 2014

Blog | Development | Interprocess Communication: Part 4
Interprocess Communication: Part 4

Welcome to part four in the IPC series! Today, we’ll be taking a look at Mutexes, Semaphores, and Wait Handles!

Mutex

Mutex is a portmanteau word of Mutual Exclusion. In the world of computing, it is often the case where a single resource needs to be shared by different consumers, but can really only service a single consumer at a time. What’s a resource? A peripheral (external device) could be considered a resource. So could a file. In the case of a software application, a resource could also be a variable within that application’s memory space.


A mutex is like someone standing guard at a single stall bathroom door. Once someone has entered the bathroom, this person’s responsibility is to make sure that nobody else opens the door until the person occupying the room has left. If there was no guard at the door (and locks did not exist), anyone could rush into the bathroom and we’d have a situation on our hands.


The wizards behind the curtain of every operating system on the market recognized the need for exclusive access to resources within a software application and baked mutexes right into the operating system.


That means that, in a language like C#, creating a mutex actually creates a special type of entity within the operating system itself. Because the mutex exists at the operating system level, multiple applications can share it by referring to the mutex by its (unique) name.

Mutex mutex;
if (!Mutex.TryOpenExisting(systemName, out mutex))
{
    Console.WriteLine("Mutex does not exist. Creating...");
    mutex = new Mutex(false, systemName);
}
else
{
    Console.WriteLine("Existing mutex opened.");
}

var doLoop = true;
do
{
    Console.WriteLine("Waiting for mutex lock...");
    mutex.WaitOne();
    Console.WriteLine("Acquired mutex.");
    Console.WriteLine("Holding lock. Press any key to release.");
    Console.ReadKey(true);
    Console.WriteLine("Lock releasing...");
    mutex.ReleaseMutex();
    Console.WriteLine("Lock released.");
    Console.Write("Re-acquire lock? y/N: ");
    doLoop = (Console.ReadLine() == "y");
} while (doLoop);

As shown above, a mutex is opened or created using a distinct name. An application must “wait” on the mutex until it is available. Once the “wait” operation completes, we can trust the mutex (guard) has successfully provided access to the unused resource (bathroom), and that any other attempts to acquire the mutex lock (open the door) will be blocked until we release the mutex (exit the bathroom).


Semaphore

Unlike the mutex, which is a single person guarding a single bathroom door, a semaphore is a single person guarding multiple bathroom doors. Given five bathroom stalls, our guard can happily allow up to five desperate patrons access to the restroom facilities without any collisions. However, once the sixth patron shows up, he will be forced to wait his turn until any one of the five occupants release the resource.

Semaphores are also defined at the operating system level, allowing processes to share the object by (unique) name. Their purpose is not to provide exclusive access to a resource by a single consumer, but rather to limit the number of consumers accessing the resource. For instance, a semaphore could be used to limit the number of threads executing a particular block of CPU-intensive code at the same time.


Semaphore semaphore;
if (!Semaphore.TryOpenExisting(systemName, out semaphore))
{
    Console.WriteLine("Semaphore does not exist. Creating...");
    semaphore = new Semaphore(2, 2, systemName);
}
else
{
    Console.WriteLine("Existing semaphore opened.");
}

var doLoop = true;
do
{
    Console.WriteLine("Waiting for semaphore lock...");
    semaphore.WaitOne();
    Console.WriteLine("Acquired semaphore.");
    Console.WriteLine("Holding lock. Press any key to release.");
    Console.ReadKey(true);
    Console.WriteLine("Lock releasing...");
    semaphore.Release();
    Console.WriteLine("Lock released.");
    Console.Write("Re-acquire lock? y/N: ");
    doLoop = (Console.ReadLine() == "y");
} while (doLoop);

Semaphores and Mutexes: Releasing Locks

While a single-use semaphore and a mutex can be consumed similarly, a crucial difference between the two is that a semaphore can be released by any process or thread, whereas a mutex must be released by the process & thread that acquired the lock. This aspect is known as Thread Identity and provides an extra layer of security & strictness to mutexes that semaphores cannot achieve natively.


WaitHandle

Wait handles are an overarching term for everything discussed thus far. In fact, the Mutex and Semaphore classes in .Net are implementations of the WaitHandle base class.

However, there exists another type of WaitHandle implementation with .Net known as the EventWaitHandle. This operating system entity is better suited at raising signals between executing code rather than meeting locking requirements. As with mutexes and semaphores, event-based wait handles can be shared between applications by means of a (unique) name.

The AutoResetEvent and ManualResetEvent are two EventWaitHandle implementations within .Net that only have one key difference – the reset mechanism. When an EventWaitHandle is unset, any thread or application which are “waiting” on it will block. Once the EventWaitHandle is set, it must be reset again for any attempts to “wait” to block; otherwise, they will simply bypass the “wait” and continue executing.

The ManualResetEvent must be manually reset, allowing any number of threads “waiting” to continue execution. The AutoResetEvent will automatically be reset after a single “waiting” thread has been allowed to continue execution. Note that if there are no threads “waiting” on the AutoResetEvent when it has been set, the next thread to begin “waiting” will continue, at which point the AutoResetEvent instance will be reset.

A third type of WaitHandle exists within .Net, known as the CountdownEvent. This type of signaling mechanism actually wraps a WaitHandle implementation to provide additional, commonly used functionality. Unlike the ManualResetEvent which, once set, no longer blocks any thread attempting to “wait”, the CountdownEvent must be set multiple times. That is, if the CountdownEvent is created with a signal count of five, then it must be set five times before the underlying WaitHandle instance actually unblocks any process “waiting” on it.

EventWaitHandle waitHandle;
if (!EventWaitHandle.TryOpenExisting(HandleName, out waitHandle))
{
    Console.WriteLine("Wait handle does not exist. Creating...");
    waitHandle = new EventWaitHandle(false, (isAuto) ? EventResetMode.AutoReset : EventResetMode.ManualReset, HandleName);
}
else
{
    Console.WriteLine("Existing wait handle opened.");
}

var doLoop = true;
do
{
    Console.Write("Toggle wait handle signal ("set" to set the signal, "reset" to reset the signal, "exit" to set and quit): ");
    switch (Console.ReadLine())
    {
        case "set":
            waitHandle.Set();
            break;
        case "reset":
            waitHandle.Reset();
            break;
        case "exit":
            waitHandle.Set();
            doLoop = false;
            break;
    }
} while (doLoop);

...

EventWaitHandle waitHandle;
if (!EventWaitHandle.TryOpenExisting(HandleName, out waitHandle))
{
    Console.WriteLine("Wait handle does not exist. Creating...");
    waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, HandleName);
}
else
{
    Console.WriteLine("Existing wait handle opened.");
}

string line = "y";
do
{
    Console.WriteLine("Waiting for wait handle signal...");
    if (waitHandle.WaitOne(1000))
    {
        Console.WriteLine("Wait handle signal received.");
        Console.Write("Wait for signal again? y/N: ");
        line = Console.ReadLine();
    }
    else
    {
        Console.WriteLine("Wait handle signal not received.");
    }
} while (line == "y");

The full source for the above example can be found here.

Stay tuned! Next time, we’ll continue the IPC Series, and look at Shared Memory using Memory Mapped Files!

Phil Azzi, Developer, GeekHive

Phil Azzi

Technical Lead
Tags
  • Patterns & Practices
  • Tutorial

Recent Work

Check out what else we've been working on