C# Lazy Initialization && Race-to-initialize?

2019-05-10 01:06发布

问题:

After reading about LazyInitializer that it's :

It offers another mode of initialization that has multiple threads race to initialize.

here is a sample :

Expensive _expensive;
public Expensive Expensive
     {
       get // Implement double-checked locking
         {
           LazyInitializer.EnsureInitialized (ref _expensive,() => new Expensive());
           return _expensive;
         }
     }

Question #1

looking at :

why #A says its implements double-checking locking ? it is just a get proeprty ?

Question #2

Does #B (lambda expression) is thread safe ?

Question #3

So i searched about this "race-to-initialize" thing by looking at a sample:

volatile Expensive _expensive;
public Expensive Expensive
{
  get
  {
    if (_expensive == null)
    {
      var instance = new Expensive();
      Interlocked.CompareExchange (ref _expensive, instance, null);
    }
    return _expensive;
  }
}

and then i thought about : isnt race to initialize is thread safe ?

e/g/ if 2 threads get into :

the expensive object will be created twice !

So again , 3 question

1)why #A says its implements double-checking locking ? it is just a get proeprty ?

2)Does #B (lambda expression) is thread safe ?

3)isnt race to initialize is thread safe

回答1:

There are various overloads of EnsureInitialized. Some accepts a synclock object (that can be null and will be created by the EnsureInitialized method). Others don't have a synclock as a parameter. All the EnsureInitialized guarantee that if called at the same time by two (or more) different threads while the object is uninitialized, the two threads will receive back a reference to the same object. So:

Expensive _expensive;

// On thread 1
LazyInitializer.EnsureInitialized (ref _expensive,() => new Expensive());

// On thread 2
LazyInitializer.EnsureInitialized (ref _expensive,() => new Expensive());

the _expensive object that will be seen by the two threads will be the same.

The only problem is that new Expensive() could be called twice (once per thread, so in a multi-thread race it could be called even more times.)

If you don't want it, use the synclock overload:

Expensive _expensive;
object _sync = null;
bool _useless;

// On thread 1
LazyInitializer.EnsureInitialized (ref _expensive, ref useless, ref _sync, () => new Expensive());

// On thread 2
LazyInitializer.EnsureInitialized (ref _expensive, ref useless, ref _sync, () => new Expensive());

Now the new Expensive() will be called only once, for every possible combination of the two (or more) threads running.