Thursday, January 31, 2008

Alternative to lock(..) use using(..)

Hi,

I have a code that sometimes need to run in single threaded environment (STE) and sometimes in multi threaded environment (MTE). In the MTE I have to set locks in order to synchronize the objects and in the STE I need to avoid locks since it is time consuming and will affect performance.

Implementation

To solve this I created a new lock mechanism:

I created an interface named ILocker:

public interface ILocker : IDisposable
{
       ILocker Lock();
}

This interface has two implementations:

One that implements a lock, for the MTE:

public class Locker : ILocker
{
       private readonly object _lock = new object();

       public ILocker Lock()
       {
           Monitor.Enter(_lock);
           return this;
       }

       public void Dispose()
       {
           Monitor.Exit(_lock);
       }
}

And another one that does nothing, for the STE

public class NoOpLocker : ILocker
{
       public ILocker Lock()
       {
           return this;
       }

       public void Dispose()
       {
       }
}

Usage

An original code of this form:

object locker = new object();

lock(locker) { ... }

Will look like:

ILocker locker = new Locker(); // or NoOpLocker();

using (locker.Lock()) { ... }

Performance

I ran 100,000,000 iterations on one thread for each locking and checked how long it takes. The test function looks like this:

void CheckLocker(ILocker locker)
{
    Stopwatch stopwatch = Stopwatch.StartNew();

    for (int i = 0; i < 100000000; i++)
    {
        using (locker.Lock())
        {
            DoNothing();
        }
    }

    Console.WriteLine(stopwatch.ElapsedMilliseconds);
}

void DoNothing() { /* Do nothing */ }

The results were:

Lock type

Time in milliseconds

No locks

10

using(..) with NoOpLocker

775

lock(..)

4865

using(..) with Locker

6603

 

Pros

- The type of lock can be determined at run time.

- The change from lock(..) to using(..) is simple.

- A class can be written with MTE in mind and can be used for STE when needed without rewriting the class.

- A class that is already written with lock(..) and used also in STE, can use using(..) and improve performance, while still supporting MTE as needed.

Cons

- The Locker works slower than just using lock(..)

- The NoOpLocker works slower than using nothing.