Synchronization with Mutexes
A mutex is similar to a lock, but:
- You can use mutexes to synchronize threads across processes.
- A mutex has thread affinity, meaning that the thread owning the mutex must release it.
- A mutex is an instantiable type.
- There are two types of mutexes: local mutexes and Named System mutexes.
Consider the following code:
class ThreadSafeType
{
int counter = 0;
Mutex mutex = new Mutex();
public void PrintThreadId()
{
mutex.WaitOne();
for (int i = 0; i < 5; i++)
{
counter++;
Console.WriteLine("Process/Thread id : " +
Process.GetCurrentProcess().Id+" /
"+Thread.CurrentThread.GetHashCode() +
" added one to counter. Counter Value : " + counter);
Thread.Sleep(1000);
}
mutex.ReleaseMutex();
Console.WriteLine(counter);
}
}
When you run the preceding code, the output looks like this:
Process/Thread id : 1484 / 3 added one to counter. Counter Value : 1
Process/Thread id : 1484 / 3 added one to counter. Counter Value : 2
Process/Thread id : 1484 / 3 added one to counter. Counter Value : 3
Process/Thread id : 1484 / 3 added one to counter. Counter Value : 4
Process/Thread id : 1484 / 3 added one to counter. Counter Value : 5
5
Process/Thread id : 1484 / 4 added one to counter. Counter Value : 6
Process/Thread id : 1484 / 4 added one to counter. Counter Value : 7
Process/Thread id : 1484 / 4 added one to counter. Counter Value : 8
Process/Thread id : 1484 / 4 added one to counter. Counter Value : 9
Process/Thread id : 1484 / 4 added one to counter. Counter Value : 10
10
Another common developer need is synchronizing access to files. The code below tries to write to a file without any synchronization mechanism implemented:
// no synchronization example
class ThreadSafeType
{
public void PrintThreadId()
{
using (StreamWriter sw = new StreamWriter("TestFile.txt," true))
{
for (int i = 0; i < 5; i++)
{
sw.WriteLine("Process/Thread id : " +
Process.GetCurrentProcess().Id + " / " +
Thread.CurrentThread.GetHashCode());
}
Thread.Sleep(1000);
}
}
}
A single process running multiple threads will throw the following IOException:
System.IO.IOException: The process cannot access the file
'D:\TestFile.txt' because it is being used by another process.
You can solve the issue by using a mutex as follows (the mutex used here is a local mutex):
// Using a local mutex for File Access
class ThreadSafeType
{
Mutex mutex = new Mutex();
public void PrintThreadId()
{
mutex.WaitOne();
using (StreamWriter sw = new StreamWriter(
"TestFile.txt," true))
{
for (int i = 0; i < 5; i++)
{
sw.WriteLine("Process/Thread id : " +
Process.GetCurrentProcess().Id + " / " +
Thread.CurrentThread.GetHashCode());
}
Thread.Sleep(4000);
}
mutex.ReleaseMutex();
}
}
Here's the output in the file after implementing the local mutex and running the preceding code:
Process/Thread id : 2560 / 3
Process/Thread id : 2560 / 3
Process/Thread id : 2560 / 3
Process/Thread id : 2560 / 3
Process/Thread id : 2560 / 3
Process/Thread id : 2560 / 4
Process/Thread id : 2560 / 4
Process/Thread id : 2560 / 4
Process/Thread id : 2560 / 4
Process/Thread id : 2560 / 4
Unfortunately, when you run two instances of this process at the same time, you still get the IOException shown previously. Instead, for inter-process synchronization, you must use named system mutexes as shown in the code below:
// Named System Mutex example
class ThreadSafeType
{
Mutex mutex = new Mutex(false, "TestFile");
public void PrintThreadId()
{
mutex.WaitOne();
using (StreamWriter sw = new StreamWriter(
"TestFile.txt," true))
{
for (int i = 0; i < 5; i++)
{
sw.WriteLine("Process/Thread id : " +
Process.GetCurrentProcess().Id + " / " +
Thread.CurrentThread.GetHashCode());
}
Thread.Sleep(3000);
}
mutex.ReleaseMutex();
}
}
After running the named system mutex code, here's the output you'll find in the file:
Process/Thread id : 3480 / 3
Process/Thread id : 3480 / 3
Process/Thread id : 3480 / 3
Process/Thread id : 3480 / 3
Process/Thread id : 3480 / 3
Process/Thread id : 4552 / 3
Process/Thread id : 4552 / 3
Process/Thread id : 4552 / 3
Process/Thread id : 4552 / 3
Process/Thread id : 4552 / 3
Process/Thread id : 3480 / 4
Process/Thread id : 3480 / 4
Process/Thread id : 3480 / 4
Process/Thread id : 3480 / 4
Process/Thread id : 3480 / 4
Process/Thread id : 4552 / 4
Process/Thread id : 4552 / 4
Process/Thread id : 4552 / 4
Process/Thread id : 4552 / 4
Process/Thread id : 4552 / 4
Note that both the processes and their threads participate.