Thursday, December 7, 2006

.Net Threads - 3 ways to synchronize data

MethodImpl

One of the simplest way is to use 'System.Runtime.CompilerServices.MethodImplAttribute' class to serialize access to the entire method.

using System.Runtime.CompilerServices;
...
[MethodImpl (MethodImplOptions.Synchronized)]
public void SomeMethod ()
{
  // Only one thread at a time may execute this method
}

Some performance factors to note here ...

  • However, using MethodImpl to synchronize is less preferable than synchronizing with Monitor, ReaderWriterLock and other System.Threading classes due to its coarseness in locking out threads
  • At best, MethodImpl locks at the object level-the equivalent of lock (this)
  • Under certain circumstances, MethodImpl locks at the type level or even at the application domain level, either of which adversely affects performance and scalability

Monitor

private object syncLock = new object();
Monitor.Enter(syncLock);
try
{
  this.y = y;
}
finally
{
  Monitor.Exit(this);
}

Notice that Monitor.Enter does not allow one to specify a timeout value to acquire the lock. TryEnter method allows you to specify this timeout interval in milliseconds. It returns a value of true if the calling thread is able to enter the monitor before the timeout interval is exceeded.

private object syncLock = new object();
bool lockAcquired = Monitor.TryEnter(syncLock, 2000);
if (lockAcquired)
{
  try
    {
      this.y = y;
  }
  finally
    {
      Monitor.Exit(this);
  }
}
else
{
  //*** supply contingency code for when timeout occurs
}

An alternate way to Monitor.Enter is to use lock(). The compiler automatically adds 'try-catch-finally' block to result in Monitor.Enter like code. This method does not take a timeout value.

public class ClassName
{
  private object syncLock = new object();

  public void DoSomething()
  {
      lock ( syncLock )
      {
          count ++;
      }
  }

  private int count;
}

ReaderWriterLock

- One of the issues with the use of Monitor class is that it does not permit access to more than one thread at a time, even if the other thread needs readonly access - ReaderWriterLock solves this problem. It allows any number of threads to read shared data concurrently but prevents overlapping reads and writes as well as overlapping writes - ReaderWriterLock associates exclusive locks with threads and track an internal lock count; thus avioding deadlocks - 'Live lock' situations happen when a writer thread waits for ever for an exclusive lock as new reader threads acquire shared lock before an existing reader thread release this shared lock. It is important to note that ReaderWriterLock are designed to automatically handle and avoid live lock situations

// read without exclusive lock
ReaderWriterLock lock = new ReaderWriterLock();
bool lockAcquired = lock.AcquireReaderLock(Timeout.Infinite);
if (lockAcquired == true)
{
  try
    {
      y = this.y;
  }
  finally
    {
      lock.ReleaseReaderLock();
  }
}

// write with exclusive lock
ReaderWriterLock lock = new ReaderWriterLock();
bool lockAcquired = lock.AcquireWriterLock(Timeout.Infinite);
if (lockAcquired == true)
{
  try
    {
      this.y = y;
  }
  finally
    {
      lock.ReleaseWriterLock();
  }
}

Note here that using AcquireWriterLock while AcquireReaderLock has been called (shared lock) in the same thread will result in thread blocked indefinately. Use UpgradeToWriterLock instead.

//*** acquire shared lock
lock.AcquireReaderLock(Timeout.Infinite);
//*** escalate shared lock to exclusive lock
lock.UpgradeToWriterLock(Timeout.Infinite);

Another scenario to consider is this ...

  • Two threads A and B: A->AcquireReaderLock then B->AcquireWriterLock then A->UpgradeToWriterLock
  • B's request is placed in queue and it waits till A releases shared lock.
  • When A->UpgradeToWriterLock, the shared lock is released and A's request for exclusive lock is placed in its queue.
  • Since B is in queue first, B get the exclusive lock. This might change the underlying data that A has.

To avoid this situation, perform read, verification and write operations using a single AcquireWriterLock operation.

References http://msdn.microsoft.com/msdnmag/issues/05/02/BasicInstincts/ http://msdn.microsoft.com/msdnmag/issues/03/01/NET/