I am willing to bet that every developer has been warned about the
dangers of developing multi-threaded operations at some point in their
career. Let's face it, we have enough to worry about with apps behaving
oddly as a result of client machine setup, network configuration, and
authorization changes than to let the complexities of threading be
added to the list. It's not that it’s such a difficult subject to
grasp, but there is plenty to understand in order to create a successful
robust implementation. As we all know, the biggest reasons that many
will advise careful consideration are well-founded because of its
unpredictable nature. Among other issues - like recovering from errors -
the complexity of operations happening at the same time is not always
very appealing to the developer. You need a way to 'synchronize'
threads so that one is not stepping on the other's toes. One attractive
way to tackle this problem is the use of waithandles.
The WaitHandle class is located in the System.Threading namespace.
Waithandles allow you to manage when, how, and how long a thread
will block and wait for access to a piece of functionality. This is
done by signaling (or notifying for you Java coders) another thread
when the current thread is done. The WaitHandle waits to be signaled
when one of the static wait methods - WaitOne(), WaitAny(), or
WaitAll() - is called. The WaitOne() method will block all other
threads until it is signals them to continue. Along with the timeout
parameter, the WaitAll and WaitAny
methods take WaitHandle
arguments that are used to signal and unblock any waiting threads.
Until the waiting threads are signaled (or the
timeout is reached), the threads will wait. If you’re developing a
process which requires simultaneous access to some shared resource,
your implementation should begin with determining how shared the
resource should be. When this is done, you can explore a couple more
options.
Derived WaitHandles: Mutexes and Semaphores
The Mutex and Semaphore classes are derived from the WaitHandle
class. With extra functionality than WaitHandle, they can give you a
more scalable solution. Unlike Monitors,
these waithandles can be named and accessed across the operating
system. Using the OpenExisting()
methods, you can access the specified named mutex or semaphore. I like
that along with these waithandles, you also get access control security
to implement when using it across application domains. You control
security by using .NET 2.0's new SetAccessControl() method and the
SemphoreSecurity and MutexSecurity classes. Take a look through
Microsoft’s documentation on these classes for more information.
As descendants of the WaitHandle class, mutexes and semaphores
signal the blocking thread for synchronization using the ReleaseMutex()
and Release() methods, respectively. The first thread entering the code
‘locks’ the functionality with a simple call to the WaitOne() method.
So what’s the difference between the two, you ask? The answer is that
semaphores can count. When the WaitOne()
method is called on multiple threads using the same mutex, only the
first thread to call the method will continue on while the other
threads wait for it to signal that it’s done. Once it’s done, the next
one will gain access and the rest will remain blocked, and so on down
the line. On the other hand, when the WaitOne() method is
called using a semaphore, it will not cause any other thread to be
blocked until a zero count is reached. You declare the max count with
the call to the constructor. So each time the WaitOne() method is
called, the semaphore count decrements by one until 0 is reached. Any
other thread blocking at the WaitOne()
line will enter the semaphore as the count increases. Depending on your
implementation, semaphores can give you even more flexibility and
scalability. And what architect wouldn't want that?
Let’s take a look at a couple of examples of how mutexes and semaphores lock:
Mutex Example
using System;
using
System.Threading;
namespace
MutexExample {
public class MutexTest {
private const int MAX = 5;
private
Mutex _mutex;
private
int _count = 0;
public void Run() {
_mutex = new
Mutex();
for
(int i = 0; i < MAX + 2; i++) {
Thread
t = new Thread(new ThreadStart(Start));
t.Start();
}
}
private
void Start() {
string
name = "Thread " + _count++;
_mutex.WaitOne();
Console.WriteLine("{0} : Entering mutex", name);
Thread.Sleep(3000);
Console.WriteLine("{0} : Exiting mutex", name);
_mutex.ReleaseMutex();
}
}
}
Semaphore Example
using System;
using
System.Threading;
namespace
SemaphoreExample {
class SemaphoreTest {
private
const int MAX =
5;
private
Semaphore _semaphore;
private
int _count = 0;
public void Run() {
_semaphore = new Semaphore(MAX,
MAX);
for
(int i = 0; i < MAX + 2; i++) {
Thread
t = new Thread(new ThreadStart(Start));
t.Start();
}
}
private
void Start() {
_semaphore.WaitOne();
string
name = "Thread " + _count++;
Console.WriteLine("{0} : Entering semaphore ", name);
Thread.Sleep(3000);
Console.WriteLine("{0} : Exiting semaphore ", name);
_semaphore.Release();
}
}
}
As you can see from these examples, mutexes and semaphores are very
simple to use. However, the same may or may not be said when actually
putting them to good use, and debugging them. If you’re anything like
me, you’re probably already thinking about 2 or 3 instances where these
WaitHandles could have helped. If you’re even more like me, you’re
probably thinking, “Why would I ever use a mutex over a semaphore.” I
would answer, “If you asked that question, you probably wouldn’t.” Most
smart developers will adhere to the ever-present goal of creating
scalable solutions where the only bounds are that of the programming
language. While this may sound facetious, the fact remains that mutexes
only allow access to one thread, while semaphores can be much more flexible.