Jan 032010
 

In continuation to my earlier post about multithreading in .NET, I am writing about the ReaderWriterLock class which is one more method of thread synchronization.

The ReaderWriterLock is based on the fact that the Monitor locking (lock keyword in C#) doesnt really make any distinction whether the thread accessing the variable is reading it or writing to it. If its only reading it, then we should not need an exclusive lock. So If we allow multiple threads to read the value, it would be much faster than having mutually exclusive locks, which means a more scalable application? On paper, yes – but not in reality.

The problem with ReaderWriterLock is with its implementation. Several experts have slammed this technique and found that outside of limited scenarios, it is actually far slower than the Monitor.Enter method used to get an exclusive lock. ReaderWriterLock gives higher priority to reader threads then writers. This makes sense if you have many readers and only a few writers. So a lot of readers are able to read the resource while the writer has to wait longer to get the lock. But what If you have equal or more writers. The process of favoring readers make writer threads queued up and take a very long time to complete.

Jeffrey Richter says that this performance loss is due to support for Recursion in the ReaderWriterLock class. Due to this, the class needs to maintain a record of the number of times each thread acquires the lock and increment and decrement the counter. When multiple reader threads acquire the same lock (remember ReaderWriterLock class allows simultaneous reads), a counter is maintained for each thread. This overhead is what causes the ReaderWriterLock to pale in comparison to the Monitor class. It is approximately 6 times slower.

Here is a code sample. The Threads 1 and 3 are reader threads while the Thread 2 is a writer one. When you take a look at the output, its clear that the readers are granted simultaneous access to the _value variable, but when the writer thread is writing to it, all the readers wait in queue patiently for it to finish executing. I have made the DoWorkRead method parametrized in order to identify the thread on which the method is being executed.

class Program
{
    int _value = 0;
    ReaderWriterLock _rwLock = new ReaderWriterLock();
 
    static void Main(string[] args)
    {
        Program _prgm = new Program();
        for (int i = 0; i < 20; i++)
        {
            Thread _t1Obj = new Thread(_prgm.DoWorkRead); //Reader Thread
            //Writer Thread
            Thread _t2Obj = new Thread(new ThreadStart(_prgm.DoWorkWrite));
            //Reader Again
            Thread _t3Obj = new Thread(_prgm.DoWorkRead); 
 
            //Start all threads
            _t1Obj.Start("Thread 1");
            _t2Obj.Start();
            _t3Obj.Start("Thread 3");
 
            //Wait for them to finish execution
            _t1Obj.Join();
            _t2Obj.Join();
            _t3Obj.Join();
        }
 
    }
 
    public void DoWorkRead(object threadName)
    {
        //Accquire Reader Lock.
        _rwLock.AcquireReaderLock(Timeout.Infinite);
        Console.WriteLine("Read start: Thread: " + threadName + " " + _value);
        if (threadName.ToString() == "Thread 1")
            //Irregular sleeps makes more chances of
            //Multiple threads trying to access it
            //at same time
            Thread.Sleep(10);
        else
            Thread.Sleep(250);
        Console.WriteLine("Read end  : Thread: " + threadName + " " + _value);
        _rwLock.ReleaseReaderLock();
        //Release Lock
    }
 
    public void DoWorkWrite()
    {
        _rwLock.AcquireWriterLock(Timeout.Infinite);
        Console.WriteLine("\nWriter start: " + _value);
        _value++; //Writing
        Console.WriteLine("Writer End: " + _value);
        _rwLock.ReleaseWriterLock();
        Console.WriteLine();
    }
 
}

A look at the output shows the once the writer acquires a lock, it is mutually exclusive, but both the reader threads are able to access the variable _value.

One more problem with the ReaderWriterLock class is that it allows Reader threads to acquire writer locks. If you set an infinite timeout, it will create a deadlock situation, where the thread just waits to get the Writer lock but cant because the very same thread holds on to the Reader lock and is yet to release it. So the application just waits and waits.

        public void DoWorkRead(object threadName)
        {
            //Accquire Reader Lock.
            _rwLock.AcquireReaderLock(Timeout.Infinite);
            Console.WriteLine("Read start: Thread: " + threadName + " " + _value);
            //NEVER EVER DO THE BELOW. IT WILL CREATE A DEADLOCK
            _rwLock.AcquireWriterLock(Timeout.Infinite);
            Thread.Sleep(10);
            Console.WriteLine("Read start: Thread: " + threadName + " " + _value);
            _rwLock.ReleaseReaderLock();
            //Release Lock
        }

Keeping all these issues in mind, Microsoft introduced a new class – the ReaderWriterLockSlim class from .NET 3.0 onwards, while leaving the old one in there as well, for backwards compatibility. This class placed Lock recursion in the hands of the developers by giving an enum that could be set during object recursion. With recursion out of the way, performance is dramatically better and comparable to the Monitor class. It also took care of the deadlock situation above. As a conclusion, it is always better to avoid the ReaderWriterLock class and use the slim implementation instead. If you are on .NET 2.0, then since that option is not open to you, its better to look at some other synchronization technique.

Dec 282009
 

How often have you faced  the irritating not responding screen while the application waits for the data that you requested for. The problem is not with bad code or a slow database, the problem with conventional applications is that all the code is executed on a single thread. i.e. whenever an operation is performed the calling function must wait for it to complete in order to resume execution.

Since the main thread is also the UI thread, the UI stops responding whenever its waiting for the function to return, making the user experience very bad. To avoid this, any time consuming operation should be executed on its own thread rather than the main thread. Lets see a simple example of a method created on another thread.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        static void Main(string[] args){
            ThreadingApp _testObj = new ThreadingApp();
 
            //Declaring Two Objects for calling the parameterless and parametrized method
            Thread _tObj1 = new Thread(new ParameterizedThreadStart(_testObj.DoSomeWork));
            Thread _tObj2 = new Thread(new ThreadStart(_testObj.DoWork));
 
            //Calling the overloaded method
            _tObj1.Start("CustomParameter");
            //Calling the parameterless method
            _tObj2.Start();
 
        }
 
    class ThreadingApp{
        // Parameterless method executed on anothed
        public void DoWork(){
            Console.WriteLine("Executed On another thread");
        }
 
        public void DoSomeWork(object _name){
            Console.WriteLine("Executed on another thread. Paramter: " + _name);
        }
    }

The ability to pass parameters to methods on other threads is new in .NET 2.0. The delegate introduced for this is the ParametrizedThreadStart delegate which takes in an object parameter. Since its an object type, pretty much anything can be passed to the method. So creating new threads is pretty simple. But a word of caution here – Threading unless used carefully can introduce very hard to detect bugs in your system.

Multiple threads don’t cause any problem till the time each thread runs its own context not depending on shared resources or variables. The issues start when threads start to modify each others variables and access each others resources. Lets see a small example where multiple threads accessing the same variable can lead to unpredictable scenarios. In the below code two variables increment a class level variable and print the values before and after the incrementing. As you can see in the output below, it leads to a chaotic scenario where threads are misreading values and coming in each others way to access the variable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    class Program
    {
    int _value = 0;
    static void Main(string[] args){
        Program _prgm = new Program();
        for (int i = 0; i > 5; i++)
        {
            Thread _t1Obj = new Thread(new ThreadStart(_prgm.DoWork1));
            Thread _t2Obj = new Thread(new ThreadStart(_prgm.DoWork2));
 
            _t1Obj.Start();
            _t2Obj.Start();
        }
 
    }
 
    public void DoWork1()
    {
        Console.WriteLine("Thread1 start: " + _value);
        _value++;
        //Introduced a small sleep to bring some entropy in the system
        Thread.Sleep(10);
        Console.WriteLine("Thread1 End: " + _value);
    }
 
    public void DoWork2()
    {
        Console.WriteLine("Thread2 start: " + _value);
        _value++;
        Console.WriteLine("Thread2 End: " + _value);
    }
 
    }

When we see the output, it looks pretty haphazard. Its easy to see the threads are confused about execution and misreading the value of the variable.

The Monitor class can be used to synchronize threads in .NET. How it works is that it puts a lock on the currently executing code making all other threads wait before they can access the variables that current method has locked. The Monitor has two main static methods Monitor.Enter and Monitor.Exit for entering the lock and exiting from it. Apart from this there is also the Monitor.TryEnter which tries to gain ownership of the code block. If its not able to it returns false. This is very important since calling the Monitor.Exit for a block you dont have ownership of, would raise a SynchronizationLockException. However making sure to call Enter and Exit every time would get cumbersome for a developer and there are chances it might be forgotten. Here is where C# comes into rescue with the lock keyword. Enclosing the code within the lock block automatically does all the steps which are mentioned above.

1
2
3
4
5
6
7
8
9
10
11
12
    static readonly object _lockObj = new object();
    public void DoWork1()
    {
        lock (_lockObj)
        {
            Console.WriteLine("Thread1 start: " + _value);
            _value++;
            //Introduced a small sleep to bring some entropy in the system
            Thread.Sleep(10);
            Console.WriteLine("Thread1 End: " + _value);
        }
    }

Enclosing the code within the lock block brings order back to the method execution and we can observe that that each thread waits for the other to complete before attempting to modify the variable. Here is the IL for the above method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.method public hidebysig instance void  DoWork1() cil managed
{
 
  IL_0000:  ldsfld     object ThreadingApp.Program::_lockObj
  IL_0005:  dup
  IL_0007:  call       void [mscorlib]System.Threading.Monitor::Enter(object)
  .try
  {
    //Our method logic removed for brevity
  }  // end .try
  finally
  {
    IL_0057:  ldloc.0
    IL_0058:  call       void [mscorlib]System.Threading.Monitor::Exit(object)
    IL_005d:  endfinally
  }  // end handler
  IL_005e:  ret
} // end of method Program::DoWork1

A very important point stressed time and again would be to make sure not to expose the locking object outside the class. If this is done, anyone can use the object to lock your methods and not release them while your application threads remain indefinitely waiting for the lock to be freed.

There are many other techniques for thread synchronization in .NET. I will be covering them in upcoming posts.