Waltzing Through the Parallel Extensions June CTP: Synchronization Primitives

June 11, 2008

2 comments

Just a few days ago, the Parallel Extensions team has released a new CTP of the Parallel Extensions for .NET 3.5, a.k.a. PFX.  This new CTP is not just a bunch of bug fixes – it’s packed with new functionality for us to explore.  (I’ve written some introduction bits on the December ’07 CTP in the past, so you might want to read them if you haven’t played with the PFX yet.)

In this post series, we will look at most of the interesting new functionality.

Synchronization Primitives

This release contains a significant number of synchronization-related primitives, providing better performance and scalability when compared to the existing .NET mechanisms.  Let’s quickly review the new APIs.

First and foremost, almost every single new mechanism introduced in this CTP features the ability to spin (i.e., burn CPU cycles in a loop) before trying to acquire the synchronization primitive.  Spinning is generally frowned upon as a means of achieving synchronization, but a small number of spins is significantly faster than a system call to acquire a kernel synchronization mechanism.  These spinning facilities are provided through the SpinWait class, which we can use when constructing our own synchronization mechanisms.  (As a side note, spinning before acquiring is not something invented by the PFX team – the critical section Win32 API features the ability to spin before acquiring the critical section through the InitializeCriticalSectionAndSpinCount function.)

The SpinLock class implements a synchronization mechanism closely related to the SpinWait class.  The general idea here is that a spinlock is not supported by any kernel synchronization mechanism – a thread that wants to acquire a spinlock will spin indefinitely until the spinlock becomes available (spinlocks have been used in the Windows kernel from the very beginning, and it was fairly easy to write one in user-mode).  The synchronization is provided by the Interlocked.CompareExchange primitive.  Note that a spinlock cannot be acquired recursively – a LockRecursionException is thrown if you attempt to do so.

The CountdownEvent class is a synchronization mechanism that is initialized with a specified counter, and provides facilities for increment and decrementing the counter.  When the counter reaches zero, the synchronization mechanism becomes signaled, thus releasing any waiting threads.  This is an extremely useful facility that previously had to be implemented using a combination of Interlocked.Decrement and a ManualResetEvent.  For example, the following code spins off four distinct tasks which decrement the event’s counter until it reaches zero and the main thread is released:

CountdownEvent countEvent = new CountdownEvent(4);

Parallel.Invoke(

    () => { …; countEvent.Decrement(); },

    () => { …; countEvent.Decrement(); },

    () => { …; countEvent.Decrement(); },

    () => { …; countEvent.Decrement(); }

);

countEvent.Wait();

The ManualResetEventSlim and SemaphoreSlim classes feature revised implementations of the familiar event and semaphore concepts.  This revised implementation relies on spinning and on using a Monitor internally, and creating a kernel synchronization primitive only as the last resort (for example, when the WaitAll or WaitAny methods are used, or when a WaitHandle is explicitly requested).  This should provide better performance for the vast majority of applications using these synchronization primitives.

Another category of synchronization mechanisms is featured by the LazyInit<T> and WriteOnce<T> structures.  The LazyInit<T> structure supports the lazy initialization paradigm in a thread-safe manner.  (This is suspiciously similar to the one-time initialization mechanism introduced in Windows Vista.)  The WriteOnce<T> structure supports a mechanism for ensuring that a variable is written to a most once, in a thread-safe manner.

The LazyInit<T> structure takes a factory function that performs the initialization when the value is first accessed, and is well-suited for lazily initializing a value that is expensive to initialize eagerly. It supports multiple modes of lazy initialization, exposed by the LazyInitMode enum.  The available options are:

  • EnsureSingleExecution – if multiple threads attempt to access the value concurrently, one of them will execute the factory function to initialize the value, and the rest will wait until the initialization completes.  (This is similar to the synchronous one-time initialization native API, with InitOnceBeginInitialize and InitOnceComplete.)
  • AllowMultipleExecution – if multiple threads attempt to access the value concurrently, all of them will begin executing the factory function to initialize the value, and the first one to succeed will signal the rest that initialization has completed and that this first value should be used.  (This is similar to the asynchronous one-time initialization native API, with the INIT_ONCE_ASYNC flag.)
  • ThreadLocal – if multiple threads attempt to access the value, each thread will execute the factory function to obtain a thread-local value that will be used on that thread only.

In the following example, the value is only initialized when accessed – this can be observed by setting a breakpoint on the second Console.WriteLine line and ensuring that the “Initializing” print-out is only executed when the value is accessed.

LazyInit<string> lazyInit = new LazyInit<string>(

    () => {

        Console.WriteLine(“Initializing”);

        return “Hello”;

    });

Console.WriteLine(lazyInit.Value);

The WriteOnce<T> structure is similar to a nullable type that can be set only once.  Any further attempts to set the value will result in an InvalidOperationException.  It also features the TryGetValue and TrySetValue methods, which work in a thread-safe fashion to ensure that you’re setting the value only once.

Debugging Aids

All of the new synchronization primitives feature debugging views that facilitate understanding the internal state of the synchronization mechanism while debugging in Visual Studio.  For example, here’s the debugger view of a countdown event:

image

And here’s the debugger view of a lazily initialized variable (yes, there are still some bugs in this CTP):

image

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

2 comments

  1. Nathan Phillip Brink (binki)January 26, 2016 ื‘ 12:04 AM

    How is LazyInitializer supposed to be any better than Lazy which also supports synchronization?

    Reply
    1. Sasha Goldshtein
      Sasha GoldshteinFebruary 14, 2016 ื‘ 5:28 PM

      Have you noticed that you are replying on a 8-year-old post that was covering a CTP release? ๐Ÿ˜€

      Reply