When you are creating multiple threads that need to access a shared resource then you need to implement some form of locking on that resource to prevent race conditions. A race condition can occur when two threads try to access the shared resource at the same time, for example if two threads run this counter = counter+1 at the same time then counter may well only get incremented by 1 as both threads just add 1 to the initial value of counter.
Here is an example of a C# Console App that has these race conditions. When the app finishes the number in firstNumber and secondNumber should be 100 however it will most likely be some random number significantly smaller than that due to the race condition.
There are various ways to solve this problem, however in this post I am going to look the most used lock types that .Net provides. It is worth noting at this point that any code samples will be C# 4 and may be slightly different from earlier versions of C#.
Monitor
This is the most common lock used and is actually the lock type that gets used when you use the “Lock” keyword in C#. Monitor locks can be placed directly on the shared resource or on a new object that you can then use to control the access to a collection of shared resources.
In order to make the above sample work using a Monitor lock it is as simple as adding a new lock object to the class and making a couple of changes to our AddNumbers method.
What this does is wait at the Monitor.Enter line until there are no locks open on the resourceLock object, it then obtains a lock on the object, makes its changes and releases the lock on the object so the next thread can begin its lock process.
Its worth noting that the above code can be shortened by using the “Lock” keyword…
Mutex
This type of lock is built in to the Window Kernel and .Net includes a wrapper class for using it. The advantages of using a Mutex lock over a Monitor are
- You can specify a timeout. This can be especially useful in preventing deadlocks.
- You can give them a name in their constructor and because they sit in the Kernel that same named lock could be used in multiple processes. This means you could have two applications using the same lock.
Because these locks sit in the Windows Kernel there is a bit more overhead in using them as .Net has to call out to unmanaged code to create and remove them. In most cases a monitor lock is sufficient but when its not the Mutex may be the right choice.
Below is an example of avoiding deadlocks in Mutex’s by specifying a maximum wait time when trying to acquire a lock.
Semaphore
A semaphore allows you to limit the number of threads that can access a shared resource at any one time. Both the Mutex and Monitor locks are opened exclusively to one thread so the Semaphore is the lock to use if you need to allow more than one thread at a time to access a shared resource. A Semaphore can be thought of as limited pool of locks.
See below for an example of creating a Semaphore that allows 3 concurrent threads to access a shared resource.
Like Mutex’s Semaphores can also be named and time out durations can be specified in the WaitOne method.
There are a number of other prime features of the locks mentioned above but this post was just a very general overview. I may do another post on things like Pulsing/Waiting and Data partitioning to show more detailed threading techniques in the future.