BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Parallel and Asynchronous
.NET 1.1+

Thread Synchronisation with the Monitor Class

The lock statement provides a simple way to block access to critical sections of code in multi-threaded and parallel software. When more flexibility is required, the members of the Monitor class can be used to achieve similar thread synchronisation.

Thread Synchronisation

In an earlier article I described the use of the lock statement. This is used to decorate single commands and blocks of code that are critical sections. Critical sections are generally within members that can be executed by multiple threads and that access some shared, mutable state. Without this form of thread synchronisation, the threads could interfere with each other, leading to race conditions. If you haven't learned about the lock statement you should read the earlier article before continuing.

The following code shows a simple example use for lock. Here, multiple threads are prevented from attempting to increment a shared counter. Without the lock, it is possible that two threads could change the value at the same time and generate inconsistent results.

lock (_lock)
{
    _counter++;
}

Internally, the lock statement uses the Monitor class to obtain locks and to block threads until a lock becomes available. When using the .NET framework version 3.5 or earlier, the above code is equivalent to the following:

Monitor.Enter(_lock);
try
{
    _counter++;
}
finally
{
    Monitor.Exit(_lock);
}

In the above sample, the static method, Monitor.Enter, attempts to obtain a lock that is based upon the _lock variable. If a lock is available, it is taken and the thread continues with the increment. If the lock has been obtained by another thread, the current thread is blocked. As with the lock statement, the locks are re-entrant; if a thread holding a lock encounters another call to Enter for the same locking object, the thread is not blocked.

Monitor.Exit releases the lock. You can see that the lock statement places this in a finally block. This ensures that the lock is released when the code within the lock statement completes, or when an exception is encountered that is not handled within the scope of the lock statement. This is why you do not need to worry about releasing locks when using the lock statement. When using the Monitor class directly, you must call Exit to release the lock and allow another thread to enter the critical section. Importantly, you must ensure that Exit is called the same number of times as Enter, otherwise the lock will remain in place indefinitely.

Let's create an equivalent for the first thread-safe locking example from the "Locking for Thread Synchronisation" article, this time using Monitor instead of lock. Create a new console application and add the following using directive to the automatically generated class.

using System.Threading;

Now we can add the code for the program. In this example two threads are started, rather than two parallel tasks. Each updates the shared counter within a critical section, protected with Monitor.Enter, ensuring that a race condition is avoided. Without the call to Enter, the final result would likely be incorrect. One possible output from the code is shown in the comment.

class Program
{
    static int _counter = 0;
    static object _lock = new object();

    static void Main(string[] args)
    {
        Thread tAdd = new Thread(AddOne);
        Thread tSub = new Thread(SubtractOne);

        tAdd.Start();
        tSub.Start();

        tAdd.Join();
        tSub.Join();

        Console.WriteLine("Final counter value is {0}.", _counter);
    }

    static void AddOne()
    {
        Monitor.Enter(_lock);
        try
        {
            int temp = _counter;
            temp++;
            Thread.Sleep(2000);
            Console.WriteLine("Incremented counter to {0}.", temp);
            _counter = temp;
        }
        finally { Monitor.Exit(_lock); }
    }

    static void SubtractOne()
    {
        Monitor.Enter(_lock);
        try
        {
            int temp = _counter;
            temp--;
            Thread.Sleep(2000);
            Console.WriteLine("Decremented counter to {0}.", temp);
            _counter = temp;
        }
        finally { Monitor.Exit(_lock); }
    }
}

/* OUTPUT

Incremented counter to 1.
Decremented counter to 0.
Final counter value is 0.

*/
30 September 2012