Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Writing Thread Safe Code in C#

3.90/5 (22 votes)
9 Jul 2009CPOL3 min read 216.2K   1.1K  
This article describe how to write thread safe code for multithreading.

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.

C#
sealed class ServerLoadBalancerEngine
{
    private static ServerLoadBalancerEngine _instance;
    private List<Server> _servers;
    private Random _random = new Random();

    // syncLock object, used to lock the code block
    private static object syncLock = new object();

    // constructor is 'private'
    private ServerLoadBalancerEngine()
    {

        // Load list of available servers
        _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)
        {
            //To ensure thread safety
            lock (syncLock)
            {
                if (_instance == null)
                {
                    _instance = new ServerLoadBalancerEngine();
                }
            }
        }
        return _instance;
    }

    // Simple, but effective load balancer
    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.

C#
class MythreadClass
{
    Thread t = null;
    public MythreadClass()
    {
        t = new Thread( new ThreadStart( PrintFunction) ); 
        t.Start();
    }

    /// Internal Thread Function
    private void PrintFunction()
    {
        Thread.Sleep( 10000 );
        Console.WriteLine( "PrintFunction Startup " + 
                "Complete trying to call lock(this)" );

        // loop forever
        while( true )
        {
            lock(this)
            {
                Console.WriteLine( "Running Internal ThreadFunction " + 
                                   "- Starting(takes 4 seconds)" );

                // do some work
                Thread.Sleep( 3000 );
                Console.WriteLine( "Running Internal PrintFunction- Complete" );
            }
        }
    }
}

Now, this class is instantiated externally, and here is how the lock is implemented:.

C#
class clsMain
{
    ///MythreadClass class Object
    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!" );

            // do some work
            Thread.Sleep( 30000 );
        }
        Console.WriteLine( "UnLocking MythreadClass Class - " + 
                "Internal Class will now Start Logging" );
    }

    /// 
    /// The main entry point for the application.
    /// 
    [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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)