Mutex
This lesson explains how the Mutex class in C# works and its related caveats.
Mutex
A Mutex
object can be acquired by a thread to exercise mutual exclusion. Protected resources or critical sections can be guarded by a Mutex
object to allow a single thread to access a protected resource or execute within a critical section.
The most important characteristic of the Mutex
class is that it enforces thread identity. A mutex can be released only by the thread that acquired it. By contrast, the Semaphore
class does not enforce thread identity. A mutex can also be passed across application domain boundaries and used for interprocess synchronization.
Ping Pong Example
As an example, we'll write a program that prints "Ping" and "Pong" in strict alternation on the console. We'll create two threads that take turns to print on the console. Let's discuss the ping()
method first which writes the "Ping" string on the console. The first question, we may ask is how to notify the two threads of whose turn it is to print on the console. We can use a boolean variable flag
to denote which thread's turn it is to write. Once a thread has taken its turn, it'll flip the boolean variable to let the other thread takes its turn.
Since the state is shared and mutated by the two threads, we must let only one thread read or write it at any time. Additionally, each thread must check for the flag
variable to flip in a busy while loop. In later chapters, we'll learn how to avoid busy waiting. The implementation for the ping()
is as follows:
void ping(){while (true){mutex.WaitOne();while (flag){// Release the mutex so that another// thread gets a chance to acquire itmutex.ReleaseMutex();// Acquire the mutex before checking// the loop conditionmutex.WaitOne();}Console.WriteLine("Ping");flag = true;mutex.ReleaseMutex();}}
Pay special attention to the while loop which tests for the flag
variable. Notice that whenever the while loop tests for the value of the flag
variable it does so while holding the mutex! Within the while loop, we first release the mutex so that the other thread can get a chance to acquire it and do a similar test and then immediately reacquire the mutex before looping over to test the while condition. The pong()
method is similar and the complete code appears in the code widget below.
using System;using System.Threading;class Demonstration{static void Main(){new MutexPingPongExample().runTest();}}public class MutexPingPongExample{Mutex mutex = new Mutex();bool flag = false;void ping(){while (true){mutex.WaitOne();while (flag){mutex.ReleaseMutex();mutex.WaitOne();}Console.WriteLine("Ping");flag = true;mutex.ReleaseMutex();}}void pong(){while (true){mutex.WaitOne();while (!flag){mutex.ReleaseMutex();mutex.WaitOne();}Console.WriteLine("Pong");flag = false;mutex.ReleaseMutex();}}public void runTest(){Thread pingThread = new Thread(() =>{ping();});Thread pongThread = new Thread(() =>{pong();});pingThread.IsBackground = true;pongThread.IsBackground = true;pingThread.Start();pongThread.Start();Thread.Sleep(10000);}}
Note that we mark the two ping and pong threads as background so that the code widget doesn't time out. When the main thread exits the two spawned threads also terminate.
Named Mutex
We can also create named system mutexes, that span multiple applications and are visible throughout the operating system. They can be used for coordinating across multiple processes. Unnamed mutexes are also known as local mutexes and restricted to a single process.
Named Mutex
We can also create named system mutexes, that span multiple applications and are visible throughout the operating system. They can be used for coordinating across multiple processes. Unnamed mutexes are also known as local mutexes and restricted to a single process.
Usage Pattern
The most common newbie mistake is to use mutex in a way such that the possibility of not releasing the mutex after acquiring it exists. For instance, consider the following snippet:
// Class variable mutexMutex mutex = new Mutex();// snippet within some methodmutex.WaitOne();try {// ...// ... Critical Section// ...mutex.ReleaseMutex();}catch (Exception e) {// Handle exception}
If there's an exception in the critical section, the acquired mutex will not be released and if the thread that locked it, is still alive, no other thread will be able to acquire the mutex, potentially causing a deadlock.
Remember to always unlock the mutex in a finally
block when appropriate.
// Class variable mutexMutex mutex = new Mutex();// snippet within some methodmutex.WaitOne();try {// ...// ... Critical Section// ...}catch (Exception e) {// Handle exception}finally {// Unlock in a finally blockmutex.ReleaseMutex();}
using System;using System.Threading;class Demonstration{static void Main(){// First argument is name of the mutex// and second specifies if the thread// creating the mutex should also own itMutex mutex = new Mutex("", false);try {mutex.WaitOne();// Critical section}catch(Exception e) {// Handle exceptions here}finally {// REMEMBER TO RELEASE THE MUTEEX IN THE// FINALLY BLOCKmutex.ReleaseMutex();}}}
Question # 1
Consider the snippet below:
Mutex mutex = new Mutex();mutex.WaitOne();mutex.WaitOne();Console.WriteLine("Program Exiting");mutex.ReleaseMutex();mutex.ReleaseMutex();
What is the outcome of the above program?
Program hangs because mutex isn’t reentrant
Program throws an exception because mutex is being locked twice in succession
Program exits normally because mutex is reentrant
using System;using System.Threading;class Demonstration{static void Main(){Mutex mutex = new Mutex();mutex.WaitOne();mutex.WaitOne();Console.WriteLine("Program Exiting");mutex.ReleaseMutex();mutex.ReleaseMutex();}}
Question # 2
Consider the snippet below:
Mutex mutex = new Mutex();mutex.WaitOne();Console.WriteLine("Program Exiting");
What is the outcome of the above program?
Program hangs because mutex isn’ released when the thread exits
Program throws an exception because mutex isn’t released before exiting
Program exits normally
using System;using System.Threading;class Demonstration{static void Main(){Mutex mutex = new Mutex();mutex.WaitOne();Console.WriteLine("Program Exiting");}}
Question # 3
Consider the snippet below:
Mutex mutex = new Mutex();Thread t1 = new Thread(() => {mutex.WaitOne();// Exit without releasing mutexConsole.WriteLine("t1 exiting");});t1.Start();t1.Join();Thread t2 = new Thread(() => {// Acquire an unreleased mutexmutex.WaitOne();Console.WriteLine("t2 exiting");});t2.Start();t2.Join();
What is the outcome of running the above snippet?
Program hangs because t1
exits without releasing the mutex
Program exits normally
AbandonedMutexException
is thrown when t2
attempts to acquire the mutex
using System;using System.Threading;class Demonstration{static void Main(){Console.WriteLine(Environment.OSVersion);Console.WriteLine(Environment.Version);Mutex mutex = new Mutex();Thread t1 = new Thread(() => {mutex.WaitOne();Console.WriteLine("t1 exiting");});t1.Start();t1.Join();Thread t2 = new Thread(() => {mutex.WaitOne();Console.WriteLine("t2 exiting");});t2.Start();t2.Join();}}
The above snippet when run completes successfully, however, running the same snippet on a Mac or Windows will result in an exception. The official documentation states the same. However, the code widget runs Unix and the differences might be a bug/portability issue.
Note that the AbandonedMutexException
is only thrown when another thread attempts to wait on a mutex that has not been released by a thread that has exited.
Question # 4
Consider the snippet below:
Mutex mutex = new Mutex();Thread t1 = new Thread(() => {try {mutex.WaitOne();DoImportantWork();mutex.ReleaseMutex();} catch(Exception) {// swallow exceptionConsole.WriteLine("Swallowing exception");}});t1.Start();t1.Join();
As tech-lead you come across this snippet for code-review, what feedback seems appropriate?
Looks good check it in.
Move the mutex.ReleaseMutex()
call to a finally block. In case, the method DoImportantWork()
throws, we neatly unlock the mutex.
Declare mutex in a using
statement which will automatically unlock the mutex when the scope ends
using System;using System.Threading;class Demonstration{static void DoImportantWork() {throw new SystemException();}static void Main(){Mutex mutex = new Mutex();Thread t1 = new Thread(() => {try {mutex.WaitOne();DoImportantWork();mutex.ReleaseMutex();} catch(Exception) {// swallow exceptionConsole.WriteLine("Swallowing exception");}});t1.Start();t1.Join();}}
Question # 5
Consider the snippet from the previous question and the developer now uses a lock
statement as follows:
Mutex mutex = new Mutex();Thread t1 = new Thread(() =>{try{lock (mutex){DoImportantWork();}}catch (Exception){// swallow exceptionConsole.WriteLine("exception swallowed");}});t1.Start();t1.Join();
What is appropriate feedback to give in the code review?
Looks good, check it in.
mutex
needs to be explcitly released in a finally
block
Release the mutex in a finally
block
Question # 6
Consider the following snippet:
using(var mutex = new Mutex()){mutex.WaitOne()DoImportantWork();mutex.ReleaseMutex();}
When code-reviewing the above snippet do you spot any bugs?
Nope. All looks good.
When the using
scope ends, it’ll call Dispose()
on the mutex, however, if DoImportantWork()
throws an exception which is swallowed, the mutex would remain locked as Dispose()
doesn’t release the mutex.
Snippet should be wrapped in try-catch
block.
Question # 7
Consider the snippet below:
Mutex mutex = new Mutex();Thread t1 = new Thread(() =>{mutex.WaitOne();mutex.WaitOne();mutex.ReleaseMutex();mutex.ReleaseMutex();});t1.Start();t1.Join();// Main thread attemps to acquire the mutexmutex.WaitOne();Console.WriteLine("All Good");mutex.ReleaseMutex();
What will be the output of running the above program?
“All Good” is printed on the console.
Program execution hangs.
AbandonedMutexException
is thrown because the mutex isn’t released as many times as it is acquired.
using System;using System.Threading;class Demonsttration{static void Main(){Mutex mutex = new Mutex();Thread t1 = new Thread(() =>{// Child thread locks the mutex// twice but releases it only oncemutex.WaitOne();mutex.WaitOne();mutex.ReleaseMutex();});t1.Start();t1.Join();// Main thread attemps to acquire the mutexmutex.WaitOne();Console.WriteLine("All Good");mutex.ReleaseMutex();}}