The following article is an excerpt from chapter 5 of the book Practical .NET2 and C#2.
The System.Threading.Monitor class allows you to make almost any portion of your code executable by only one thread at a time. We call such a block of code a critical section. In further articles extracted from Practical .NET2 and C#2, we'll introduce other means to synchronize access to resources, such as Win32 synchronization objects, the System.Threading.ReaderWriterLock class, the System.Runtime.Remoting.Contexts.SynchronizationAttribute attribute, volatile fields, and the System.Threading.Interlocked class.
System.Threading.Monitor
System.Threading.ReaderWriterLock
System.Runtime.Remoting.Contexts.SynchronizationAttribute
volatile
System.Threading.Interlocked
The Monitor class offers the Enter(object) and Exit(object) static methods. These methods take a reference to an object as a parameter. This object constitutes a simple means to uniquely identify the resource which needs to be accessed in a synchronized way. When a thread calls the Enter() method, it waits to have the exclusive access right to the referenced object (it only waits if another thread already has this right). Once this right has been acquired and consumed, the thread releases this right by calling the Exit() on this same object. Notice that using these methods is not that simple, here are some caveats:
Monitor
Enter(object)
Exit(object)
Enter()
Exit()
finally
The following example illustrate how to use these two methods to protect a static field named counter from concurrent access:
counter
using System.Threading; class Program { static long counter = 1; static void Main() { Thread t1 = new Thread( f1 ); Thread t2 = new Thread( f2 ); t1.Start(); t2.Start(); t1.Join(); t2.Join(); } static void f1() { for (int i = 0; i < 5; i++){ Monitor.Enter( typeof( Program ) ); try{ counter *= counter; } finally{ Monitor.Exit( typeof( Program ) ); } System.Console.WriteLine("counter^2 {0}", counter); Thread.Sleep(10); } } static void f2() { for (int i = 0; i < 5; i++){ Monitor.Enter( typeof( Program ) ); try{ counter *= 2; } finally{ Monitor.Exit( typeof( Program ) ); } System.Console.WriteLine("counter*2 {0}", counter); Thread.Sleep(10); } } }
It is tempting to write counter instead of typeof(Program) but counter is a static member of a value type. We’ll see a bit later that there is a better solution which avoids calling the expensive typeof(Program).
typeof(Program)
Notice that the square and doubling operations are not commutative and thus the final value of counter will not be deterministic.
The C# language presents the lock keyword which is an elegant alternative to using the Enter() and Exit() methods. Our program could then be rewritten like this:
lock
using System.Threading; class Program { static long counter = 1; static void Main() { Thread t1 = new Thread(f1); Thread t2 = new Thread(f2); t1.Start(); t2.Start(); t1.Join(); t2.Join(); } static void f1() { for (int i = 0; i < 5; i++){ lock( typeof(Program) ) { counter *= counter; } System.Console.WriteLine("counter^2 {0}", counter); Thread.Sleep(10); } } static void f2() { for (int i = 0; i < 5; i++){ lock( typeof(Program) ) { counter *= 2; } System.Console.WriteLine("counter*2 {0}", counter); Thread.Sleep(10); } } }
As with for and if, the blocks defined by the lock keyword are not required to use curly braces if they only contain a single instruction. We could have written:
for
if
... lock( typeof( Program ) ) counter *= counter; ...
The use of the lock keyword provokes the creation by the C# compiler of a try/finally block which allows you to anticipate any exception which might be raised. You can verify this by using the Reflector or the ildasm.exe tool.
try/finally
As with the previous examples, we generally use the Monitor class with an instance of the Type class within a static method. In the same way, we will often synchronize using the this keyword within a non-static method. In both cases, we synchronize ourselves on an object which is visible from outside of the class. This can cause problems if other parts of the code synchronizes itself on these objects. To avoid such potential problems, we recommend that you use a private member named SyncRoot of type object, which is static or not depending on your needs:
Type
this
SyncRoot
object
class Foo { private static object staticSyncRoot = new object(); private object instanceSyncRoot = new object(); public static void StaticFct() { lock ( staticSyncRoot ) { /*...*/ } } public void InstanceFct() { lock ( instanceSyncRoot ) { /*...*/ } } }
The System.Collections.ICollection interface offers the object the SyncRoot{get;} property. Most of the collection classes (generic or not) implement this interface. Also, you can use this property to synchronize your access to the elements of a collection. Here, the SyncRoot pattern is not really applied since the object on which we synchronize the access is not private:
System.Collections.ICollection
SyncRoot{get;}
using System.Collections.Generic; using System.Collections; public class Program { public static void Main() { List list = new List(); // ... lock ( ( (ICollection) list ).SyncRoot ) { foreach (int i in list) { /* ... */ } } } }
A thread-safe class is a class where each instance cannot be accessed by more than one thread at a time. To create such a thread-safe class, you simply need to apply the SyncRoot pattern that we have seen, to its methods. A good way not to burden the code of a class that we wish to be thread-safe is to provide a thread-safe wrapper derived class like this:
class Foo { private class FooSynchronized : Foo { private object syncRoot = new object(); private Foo m_Foo; public FooSynchronized( Foo foo ) { m_Foo = foo; } public override bool IsSynchronized { get { return true; } } public override void Fct1(){lock( syncRoot ) { m_Foo.Fct1(); } } public override void Fct2(){lock( syncRoot ) { m_Foo.Fct2(); } } } public virtual bool IsSynchronized { get { return false; } } public static Foo Synchronized(Foo foo){ if( ! foo.IsSynchronized ) return new FooSynchronized( foo ); return foo; } public virtual void Fct1() { /*...*/ } public virtual void Fct2() { /*...*/ } }
public static bool TryEnter(object [,int] )
This method is similar to Enter() but it is non-blocking. If the exclusive access rights are already owned by another thread, this method returns immediately with a return value of false. We can also make a call to TryEnter(), blocking for a limited period of time specified in milliseconds. Since the result of this method is uncertain, and that in the case where we would have acquired exclusive access rights, you have to release it in a finally clause, it is recommended to immediately exit the calling function in the case where TryEnter() failed:
false
TryEnter()
using System.Threading; class Program { private static object staticSyncRoot = new object(); static void Main() { // Comment this line to test the case where the // ‘TryEnter()’ method returns true. Monitor.Enter( staticSyncRoot ); Thread t1 = new Thread(f1); t1.Start(); t1.Join(); } static void f1() { if( ! Monitor.TryEnter( staticSyncRoot ) ) return; try { // ... } finally { Monitor.Exit( staticSyncRoot ); } } }
public static bool Wait( object [,int] ) public static void Pulse( object ) public static void PulseAll( object )
The Wait(), Pulse(), and PulseAll() methods must be used together, and cannot be properly understood without a small scenario. The idea is that a thread with exclusive rights to an object decides to wait (by calling Wait()) until the state of the object changes. For this, this thread must accept to temporarily lose the exclusive access rights to the object in order to allow another thread to change its state. This thread must signal such a change by using the Pulse() method. Here is a little scenario which illustrates this in more detail:
Wait()
Pulse()
PulseAll()
T1
OBJ
Wait(OBJ)
T2
Enter(OBJ)
Pulse(OBJ)
Exit(OBJ)
If Wait(OBJ) is called by a thread who has called Enter(OBJ) several times, this thread will need to call Exit(OBJ) the same number of times to release the access rights to OBJ. Even in this case, a single call to Pulse(OBJ) by another thread will be sufficient to unblock the first thread.
The following program illustrates this functionality through two threads ping and pong which use the access rights to a ball object in an interlaced way:
ping
pong
ball
using System.Threading; public class Program { static object ball = new object(); public static void Main() { Thread threadPing = new Thread( ThreadPingProc ); Thread threadPong = new Thread( ThreadPongProc ); threadPing.Start(); threadPong.Start(); threadPing.Join(); threadPong.Join(); } static void ThreadPongProc() { System.Console.WriteLine("ThreadPong: Hello!"); lock ( ball ) for (int i = 0; i < 5; i++){ System.Console.WriteLine("ThreadPong: Pong "); Monitor.Pulse( ball ); Monitor.Wait( ball ); } System.Console.WriteLine("ThreadPong: Bye!"); } static void ThreadPingProc() { System.Console.WriteLine("ThreadPing: Hello!"); lock ( ball ) for(int i=0; i< 5; i++){ System.Console.WriteLine("ThreadPing: Ping "); Monitor.Pulse( ball ); Monitor.Wait( ball ); } System.Console.WriteLine("ThreadPing: Bye!"); } }
This program displays the following (in a non-deterministic way):
ThreadPing: Hello! ThreadPing: Ping ThreadPong: Hello! ThreadPong: Pong ThreadPing: Ping ThreadPong: Pong ThreadPing: Ping ThreadPong: Pong ThreadPing: Ping ThreadPong: Pong ThreadPing: Ping ThreadPong: Pong ThreadPing: Bye!
The pong thread does not end and remains blocked on the Wait() method. This results from the fact that the pong thread has obtained exclusive access rights on the ball object in the second place.