RSS 2.0

Personal Info:

Joe Send mail to the author(s) works on parallel libraries, infrastructure, and programming models in Microsoft's Developer Division.

Blogroll:
Other
News
 C|Net
 Kuro5hin
 The Register
Technology
 <?xmlhack?>
 Daily WTF
 DevX
 Hacknot
 Java Today
 Microsoft Top 10 Downloads
 MSDN
 MSDN: "Longhorn"
 MSDN: XML Developer Center
 Slashdot
 Techdirt
 theserverside.com
 W3C
 Web Pages That Suck
 XML Cover Pages
 XML Journal
 xml.com
Technology Blogs
 Aaron Skonnard [PluralSight]
 Adam Bosworth [Google]
 Andy Rich [MS/C++]
 Arpan Desai [MS/XML]
 BCL Team [MS]
 Bill Clementson [Lisp]
 Bill de hÓra
 Bruce Eckel [J]
 Bruce Tate [J]
 Casey Chestnut
 Cedric Beust [Google]
 Chris Anderson [MS/Avalon]
 Chris Lyon [MS]
 Christian Weyer
 Clemens Vasters [newtelligence]
 Craig Andera [PluralSight]
 Dan Sugalski [Parrot]
 Daniel Cazzulino
 Dave Chappel
 Dave Roberts [Lisp]
 Dave Thomas [PragProg]
 Dave Winer
 Dion Almaer [J]
 Don Demsak
 Doug Purdy [MS/Indigo]
 Drew Marsh
 Eric Gunnerson [MS]
 Eric Rudder [MS]
 Eric Sink
 Fritz Onion [PluaralSight]
 Gavin King [J/Hibernate]
 Grady Booch [IBM]
 Hervey Wilson [MS/Indigo]
 Hillel Cooperman [MS/Shell]
 Howard Lewis Ship [J/Apache]
 Ingo Rammer [PluralSight]
 James Gosling [J/Sun]
 James Strachan [J/Groovy]
 Jason Matusow [MS/OSS]
 Jeffrey Schlimmer [MS/Indigo]
 Joe Beda [Google]
 Joel Spoelsky
 Jon Udell
 Josh Ledgard [MS/Evang]
 Joshua Allen [MS]
 Lambda
 Larry Osterman [MS]
 Maoni Stephens [MS/CLR]
 Mark Fussell [MS/XML]
 Martin Fowler
 Martin Gudgin [MS/Indigo]
 Me
 Michael Howard [MS]
 Miguel de Icaza [Mono]
 Mike Clark
 Omri Gazitt [MS/Indigo]
 Pat Helland [MS/PAG]
 Pinku Surana
 Raymond Chen [MS]
 Rich Lander [MS/CLR]
 Rob Howard
 Rob Relyea [MS/Avalon]
 Robert Cringely
 S. Somasegar [MS/DevDiv]
 Sam Gentile
 Scoble [MS/Evang]
 Scott Guthrie [MS/WebNet]
 Scott Hanselman
 Sean McGrath [J]
 Simon Fell
 Stanley Lippman [MS/C++]
 Steve Maine
 Steve Swartz [MS/Indigo]
 Steve Vinoski
 Steven Clarke [MS/Usability]
 Stuart Halloway
 Ted Leung
 Ted Neward [DM]
 Tim Bray [Sun]
 Tim Ewald [Mindreef]
 Tim O'Reilly
 Werner Vogels [Amazon]
 Wintellect
 Yasser Shohoud [MS/Indigo]
Top 20
 Brad Abrams [MS/CLR]
 Chris Brumme [MS/CLR]
 Chris Sells [MS/Ultra]
 Cyrus Najmabadi [MS/C#]
 Dominic Cooney [MS/XAF]
 Don Box [MS/Ultra]
 Don Syme [MS/R]
 Guido van Rossum [Python]
 Herb Sutter [MS/C++]
 Ian Griffiths
 Jason Zander [MS/CLR]
 Jim Hugunin [MS/CLR]
 Joel Pobar [MS/CLR]
 Krzysztof Cwalina [MS/CLR]
 Patrick Logan
 Paul Graham
 Rico Mariani [MS/CLR]
 Rory Blyth [MS/DN]
 Sam Ruby
 Wesner Moise
VC/Business Blogs
 Ed Sim
 Fred Wilson
 Jonathan Schwartz [J/Sun]
 Lawrence Lessig [Stanford]
 Mark Cuban
 Michael Hyatt
 Pierre Omidyar
 Ross Mayfield
 VentureBlog
 Weekly Read
Wine, Food & Tea
 The Silk Road of Wine
 Vinography: a wine blog
 Wine Whys

Disclaimer:
The content of this site are my own personal opinions and do not represent my employer's view in anyway.

© 2008, Joe Duffy

 
 Saturday, June 09, 2007

Windows Vista has a new one-time initialization feature, which I’m pretty envious of being someone who writes most of his code in C# and answers countless questions about double-checked locking in the CLR.  Rather than sprinkling double-checked locking all over your code base, along with the ever-lasting worry in the back of your mind that you’ve gotten the synchronization incorrect, it's a better idea to consolidate it into one place.

That’s the purpose of the LazyInit<T> and LazyInitOnlyOnce<T> structs below.  Both let you specify an “initialization” routine (as a delegate) which gets invoked at the appropriate time to lazily initialize the state.   The only difference between the two is that LazyInit<T> might invoke your delegate more than once, due to races, but it will ensure only one value “wins”.  LazyInitOnlyOnce<T> does the extra work to ensure the initialization routine only gets called once, though at a slightly higher cost: we might need to block a thread, which means allocating a Win32 event.

Why the two?  I had originally written this with a Boolean specified at construction time to pick one over the other, but this required an extra object field which, for LazyInit<T> which was never used, along with a Boolean field.  I defined both as structs to make them super lightweight to use, and getting rid of the extra two fields seemed worth the extra baggage of an extra class, given that such a type could end up used very pervasively throughout a large code-base.  As it stands, LazyInit<T> is just the size of a pointer plus the size of T.  LazyInitOnlyOnce<T> adds one additional pointer to that.

To start with, both use the same Initializer<T> delegate:

public delegate T Initializer<T>();

And here’s LazyInit<T>, the simpler of the two:

public struct LazyInit<T> where T : class {
    private Initializer<T> m_init;
    private T m_value;

    public LazyInit(Initializer<T> init) {
        m_init = init;
        m_value = null;
    }

    public T Value {
        get {
            if (m_value == null) {
                T newValue = m_init();
                if (Interlocked.CompareExchange(ref m_value, newValue, null) != null &&
                        newValue is IDisposable) {
                    ((IDisposable)newValue).Dispose();
                }
            }

            return m_value;
        }
    }
}

Note that T is constrained to a reference type, so that we can use a null check to determine when initialization is needed.  We could have used a separate Boolean, but this would required adding another field as well as considering some trickier memory model issues.

If the Interlocked.CompareExchange fails, it means we lost the lazy initialization race with another thread, and thus just return the value the other thread produced.  We also Dispose of the garbage object if it implements IDisposable.  This pattern is very common in lazy initialization scenarios, like allocating an expensive kernel object lazily on demand.  We’d prefer to get rid of it right away since we know it will never be used.

I wish there was a way to make boxing a compile-time error for some value types.  Clearly you don't ever want to box one of these, because making a copy will entirely break the synchronization guarantees.

I’ve omitted some error checking, like ensuring m_init actually got initialized to a non-null value.

Say you need a lazily initialized event on your object.  You would just do this:

public class C {
    private LazyInit<EventWaitHandle> m_event;
    private object m_otherState;
    public C() {
        m_event = new LazyInit<EventWaitHandle>(
                delegate { return new ManualResetEvent(false); });
        m_otherState = ...;
    }
    ...
    private void DoSomething() {
        ...
        if (... need to set the event ...)
            m_event.Value.Set();
    }

}

And lastly, here’s LazyInitOnlyOnce<T>:

public struct LazyInitOnlyOnce<T> where T : class {
    private Initializer<T> m_init;
    private T m_value;
    private object m_syncLock;

    public LazyInitOnlyOnce(Initializer<T> init)
    {
        m_init = init;
        m_value = null;
        m_syncLock = null;
    }

    public T Value {
        get {
            if (m_value == null) {
                object newSyncLock = new object();
                object syncLockToUse = Interlocked.CompareExchange(
                        ref m_syncLock, newSyncLock, null);
                if (syncLockToUse == null)
                    syncLockToUse = newSyncLock;
                lock (syncLockToUse) {
                    if (m_value == null)
                        m_value = m_init();
                    m_syncLock = null;
                    m_init = null;
                }
            }

            return m_value;
        }
    }
}

We use a monitor to ensure mutual exclusion.  I lazily allocate the object used for synchronization, but this is clearly a tradeoff.  We pay for the added complexity to the code and the extra interlocked instruction (on the slow path), but avoid having to allocate an extra object when we create the struct itself and keep it alive, when we might not ever need it.  There’s already an allocation for the delegate, but this just means there’s one instead of two.

It may also not be obvious why I null out the m_syncLock field before exiting.  If we don't, the object will remain live as long as the lazily initialized variable remains live.  We want the object to be GC'd as soon as possible, because it is no longer needed.

You can use a class constructor in .NET to acheive a similar effect.  Static field initializers, however, execute in the class constructor, meaning if you have multiple lazily initialized objects or static methods, they all get initialized at once.  This is much more like LazyInitOnlyOnce<T> than LazyInit<T>, since the CLR uses locks to prevent the class constructor from running on multiple threads at once.

Anyway, there’s very little that is novel here.  But I do believe having these primitives in the .NET Framework would be immensely useful.  It would at least help steer people towards the recommended and most efficient lazy initialization pattern, which is to use double-checked locking, rather than having them possibly pursue more complicated designs.  It also removes the need to worry about volatile and Thread.MemoryBarrier, for those that aren't knowledgeable of the work we did in the CLR 2.0 to ensure double-checked locking works properly.  Lastly, it has the added benefit of getting rid of tricky calls to Interlocked.CompareExchange and lock statements scattered throughout your code, in favor of something more declarative.  What do you think?

6/9/2007 2:09:14 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [12]

 

Recent Entries:

Search:

Browse by Date:
<June 2007>
SunMonTueWedThuFriSat
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

Browse by Category:

Notables:

Currently Up To:

Reading...

Listening...

Watching...