共用方式為


Coordination Data Structures – LazyInit

This is an article in a series of blog entries describing a set of new Coordination Data Structures (CDS) introduced in the June 2008 CTP of the Parallel Extensions for .NET Framework .

LazyInit<T> provides support for several common patterns of lazy initialization. In here we will explore some of those patterns, but first a point or two regarding lazy initialization:

- Lazy initialization is often used when the initialization process is an expensive activity which is not always required. With this approach, the execution of the initialize procedure is deferred to immediately before when it is required.

- In order to improve the start-up performance of an application, one may decide to defer some of the initialization activities.

The first pattern covered by LazyInit<T> is the optimistic lazy instantiation pattern. In this pattern, multiple instances of T may be created but only one of those instances is published for all threads to access. In pseudo code, the implementation of such structure may look similar to this:

public class Singleton

{

  static volatile Singleton _instance;

  private Singleton()

  {

    // stuff ...

  }

  public static Singleton Instance

  {

    get

    {

      if (_instance == null)

      {

        Singleton local = new Singleton();

        if (Interlocked.CompareExchange<Singleton>(

          ref _instance, local, null) != null)

        {

          IDisposable disposable = local as IDisposable;

          if (disposable != null)

          {

            disposable.Dispose();

          }

        }

      }

      return _instance;

    }

  }

}

As you can see, unwanted instances are disposed automatically. This optimistic approach to concurrency removes the need for a full lock, but has the potential of multiple instantiations.

The LazyInit<T> struct offers a similar functionality. Simply create an instance of the LazyInit<T> using its default constructor:

LazyInit<Company> s = new LazyInit<Company>();

// do some stuff here

// ...

Company c = s.Value;

The above example uses reflection to create an instance of Company when the Value property is first accessed. Obviously Company must have a public parameter-less constructor for this to succeed.

It is worth emphasising that the default behaviour of LazyInit does not prevent the instantiation of more than one Company object by multiple threads concurrently accessing the Value property when the value is not yet set. However it guarantees that only one of those instances is published for all threads to access.

Another pattern offered by LazyInit<T> is the pessimistic lazy initialization pattern. This is very similar to the well-known double-check locking pattern and in pseudo code looks like this:

public class Singleton

{

  static volatile Singleton _instance;

  static object s_lock = new object();

  private Singleton()

  {

    // stuff ...

  }

  public static Singleton Instance

  {

    get

    {

      // double-check locking pattern

      if (_instance == null)

      {

        lock (s_lock)

        {

          if (_instance == null)

          {

            _instance = new Singleton();

            return _instance;

          }

        }

      }

      return _instance;

    }

  }

}

As you can see, no more than a single instance of Singleton is ever instantiated. This is achieved at the cost of acquiring a lock.

LazyInit<T> also provides a similar functionally. Here are some variations of this pattern implemented using LazyInit<T>:

Pessimistic Lazy Instantiation:

LazyInit<Company> s = new LazyInit<Company>(

  () => new Company(), LazyInitMode.EnsureSingleExecution);

// do some stuff here

// ...

Company c = s.Value;

Pessimistic Lazy Initialization:

In the example below, a database connection is opened only when needed. The connection is then kept open and no other connection is created for this instance of DataAccess:

class DataAccess

{

  LazyInit<SqlConnection> cnn;

  public DataAccess()

  {

    cnn = new LazyInit<SqlConnection>(

      delegate

      {

        var c = new SqlConnection(

          "Server=.;Database=Northwind;Integrated Security=true");

        c.Open();

        return c;

      }, LazyInitMode.EnsureSingleExecution);

  }

  public int GetProductCount()

  {

    // perform a db query: this is when we actually

    // create and open a connection to the database

    using (var cmd = new SqlCommand(

      "select count(*) from products", cnn.Value))

    {

      return (int)cmd.ExecuteScalar();

    }

  }

}

Another pattern offered by LazyInit<T> allows an initialization per thread such that each thread will get its own value. In this pattern, the value from the initialization is stored in the thread local storage (TLS):

LazyInit<Company> l = new LazyInit<Company>(

  () => new Company { ID = Guid.NewGuid() },

  LazyInitMode.ThreadLocal);

// l.Value access from the main thread

Console.WriteLine(l.Value.ID);

ThreadPool.QueueUserWorkItem(delegate

  {

  // l.Value access from a ThreadPool thread

  Console.WriteLine(l.Value.ID);

  });

In the example above, the main thread and the ThreadPool thread each create a different instance of Company with different IDs:

 

Now that you have seen some different lazy initialization patterns, it is time to fully introduce the LazyInitMode enum that can be specified as a parameter to the LazyInit<T> constructor:

LazyInitMode Value

Description

AllowMultipleExecution

The initialization function may be executed multiple times if multiple threads race to initialize the value, but only one value will be published for all threads to access.

EnsureSingleExecution

The initialization function will only be executed once, even if multiple threads race to initialize the value. This value will be published for all threads to access.

ThreadLocal

The initialization function will be invoked once per thread such that each thread will get its own published value.

 

One last point, LazyInit<T> is a value type and does not have the overhead of being a class. However you need to be careful about access patterns. If you accidentally make a copy of the struct, you’ll be copying by value meaning that you will be using a replica rather than the original.

(Thanks to Stephen Toub, Joe Duffy and Ed Essey for their input and support)

Comments

  • Anonymous
    June 02, 2008
    PingBack from http://mdavey.wordpress.com/2008/06/02/coordination-data-structures/

  • Anonymous
    July 21, 2008
    Nice article. I think DataAccess is a little confusing, in that it is not a singleton and you have a public constructor. The user could open many connections to the database by declaring many instances of this class.