Introduction
In multi-threaded applications where multiple threads make calls to the methods of a single object, it is necessary that those calls be synchronized. If code is not synchronized, then one thread might interrupt another thread and the object could be left in an invalid state. A class whose members are protected from such interruptions is called thread-safe.
Ways to implement
In the .NET Framework, there are many primitives available that you can use to make synchronized calls to shared data. One of them is the Monitor
class. The Monitor.Enter()
and Monitor.Exit()
methods implement a critical section block.
Use the Enter
and Exit
methods to mark the beginning and end of a critical section. If the critical section is a set of contiguous instructions, then the lock acquired by the Enter
method guarantees that only a single thread can execute the enclosed code with the locked object. In this case, it is recommended you place those instructions in a try
block and place the Exit
instruction in a finally
block. This facility is typically used to synchronize access to a static or instance method of a class. If an instance method requires synchronized thread access, it invokes the Enter
and corresponding Exit
methods using the current instance as the object to lock. Since only one thread can hold the lock on the current instance, the method can only be executed by one thread at a time. Static methods are protected in a similar fashion using the type of the current instance as the locked object.
If a critical section spans an entire method, the locking facility described above can be achieved by placing System.Runtime.CompilerServices..::.MethodImplAttribute
on the method, and specifying the synchronized value in the constructor of MethodImplAttribute
. Using this attribute, the Enter
and Exit
statements are not needed.
A second option is a lock
statement to implement a critical section.
There are certain conditions where a Monitor
gives you the necessary control. The Monitor
class provides the Wait
, Pulse
, and PulseAll
methods to implement timeout, signaling, and a consumer/producer design. We can't achieved these by implementing a lock
statement.
Using the code
ServerLoadBalancerEngine
implements the Singleton pattern and here we need to ensure that multiple thread calls to GetServerLoadBalancerEngine
should be synchronized.
The static object syncLock
is used to implement a critical section in the GetServerLoadBalancerEngine
function.
To synchronise static methods, we need to lock the private static object variable. Locking GetServerLoadBalancerEngine
ensures that only one thread can operate on this method, so only one instance will be created of the ServerLoadBalancerEngine
class.
sealed class ServerLoadBalancerEngine
{
private static ServerLoadBalancerEngine _instance;
private List<Server> _servers;
private Random _random = new Random();
private static object syncLock = new object();
private ServerLoadBalancerEngine()
{
_servers = new List<Server>
{
new Server{ Name = "Server1", IPAddress = "120.14.220.18" },
new Server{ Name = "Server2", IPAddress = "120.14.220.19" },
new Server{ Name = "Server3", IPAddress = "120.14.220.20" },
new Server{ Name = "Server4", IPAddress = "120.14.220.21" },
};
}
public static ServerLoadBalancerEngine GetServerLoadBalancerEngine()
{
if (_instance == null)
{
lock (syncLock)
{
if (_instance == null)
{
_instance = new ServerLoadBalancerEngine();
}
}
}
return _instance;
}
public Server NextServer
{
get
{
int r = _random.Next(_servers.Count);
return _servers[r];
}
}
}
Deadlock conditions when implementing a lock statement
Never lock the type that is GetType(MyType)
and the instance (this
), by doing so there are chances of a deadlock. To lock a static method, use a private static object, and for a private object, lock the instance method.
When locking a class instance, this will work fine if it is exposed externally and used.
Suppose I have written MythreadClass
and locking this to implement a thread safety.
class MythreadClass
{
Thread t = null;
public MythreadClass()
{
t = new Thread( new ThreadStart( PrintFunction) );
t.Start();
}
private void PrintFunction()
{
Thread.Sleep( 10000 );
Console.WriteLine( "PrintFunction Startup " +
"Complete trying to call lock(this)" );
while( true )
{
lock(this)
{
Console.WriteLine( "Running Internal ThreadFunction " +
"- Starting(takes 4 seconds)" );
Thread.Sleep( 3000 );
Console.WriteLine( "Running Internal PrintFunction- Complete" );
}
}
}
}
Now, this class is instantiated externally, and here is how the lock is implemented:.
class clsMain
{
private MythreadClass theClass = MythreadClass ();
public ClassMain()
{
Console.WriteLine( "Locking MythreadClass Class " +
"before Internal ThreadFunction does " );
lock( theClass )
{
Console.WriteLine( "MythreadClass Class Locked - " +
"we have inadvertently deadlocked the Internal Class!" );
Thread.Sleep( 30000 );
}
Console.WriteLine( "UnLocking MythreadClass Class - " +
"Internal Class will now Start Logging" );
}
[STAThread]
static void Main(string[] args)
{
clsMain cm = new clsMain();
Console.WriteLine( "Press Return to exit" );
Console.ReadLine();
}
}
By locking the instance of MythreadClass
, MythreadClass
gets deadlocked.