Fork/Join parallelism with .NET CountdownEvent

Fork/Join parallelism with .NET CountdownEvent

  • Comments 6

A common asynchronous pattern in code today is the pattern known as fork/join parallelism. This typically manifests by starting n pieces of work and later joining with that work.

The existing set of the .NET synchronization primitives does not offer a simple solution for handling this common scenario. It can be achieved, for example, by using a ManualResetEvent and a separate count that’s manipulated with Interlocked operations, but the implementations are not trivial, and they easily could lead to race conditions and other potential problems.

Parallel Extensions to the .NET Framework introduces several new synchronization and coordination primitives, one of which is meant to address this common pattern: CountdownEvent.

The CountdownEvent is a synchronization primitive that allows a thread to wait until one or more threads finish some other operations. It is similar in concept to the CountDownLatch that Java supports.

CountdownEvent is initialized with an integral count, typically greater than 0. Threads can block waiting on the event until the count reaches 0, at which point the CountdownEvent will be set. The count is decremented using CountdownEvent’s Decrement method, which is typically called by a worker thread completing a unit of work being tracked by the event.

CountdownEvent’s Decrement method has two overloads. The simplest overload decrements the count by 1, whereas the other overload allows the caller to provide a value signifying how much to decrement the count. The result with either is the same, however: when the CountdownEvent‘s count reaches 0, any waiting threads will be woken up.

CountdownEvent ctDownEv = new CountdownEvent(10);

ctDownEv.Decrement(5);

In order to improve performance and scalability, a spin-waiting mechanism is built into CountdownEvent’s Wait method. This can help for scenarios where waits will be satisfied very quickly, and thus the extra cost of busy waiting a little is less than the costs associated with unnecessary context switches and kernel transitions.

While CountdownEvent does offer a WaitHandle property, it is only intended for scenarios where a true WaitHandle is needed (such as when using WaitHandle.WaitAll to wait on a CountdownEvent and another type of primitive);  for most waiting scenarios, you should plan to use the Wait methods

Using a CountdownEvent to implement a fork/join scenario could be written like:

const int NUMBER_OF_WORKER_THREADS = 10;

void CountDownEventForkJoin()

        {

            using (CountdownEvent signalEv =

            new CountdownEvent(NUMBER_OF_WORKER_THREADS - 1))

            {

                for (int i = 1; i < NUMBER_OF_WORKER_THREADS; i++)

                {

                    int currentIndex = i;

                    ThreadPool.QueueUserWorkItem(

                        delegate

                        {

                            try

                            {

                                DoSomeWork(currentIndex);

                            }

                            finally

                            {

                                signalEv.Decrement();

                               //more work can be done here if needed

                            }

                        });

                }

                //main thread do some work

                DoSomeWork(0);

                //waits for the worker threads 

                signalEv.Wait();

            }

        }

For supporting scenarios when the developer does not know from the beginning how many asynchronous operations will be used, the CountdownEvent offers an Increment method. A worker thread can decide to split its work in more parts that will be run in parallel for better performance. (However, the Increment method cannot be called on an already signaled event.)

Such a scenario could be implemented like below:

void CountDownEventIncrement()

        {

            string[] myData = new string[]{"a", "b", "c"};

            //start with only 1 intended worker threads

            using (CountdownEvent signalEv = new CountdownEvent(1))

            {        

                    //split the worker threads

                    foreach (string s in myData)

                    {

                        //a new worker will do some work

                        signalEv.Increment();

                        ThreadPool.QueueUserWorkItem(

                            delegate(object args)

                            {

                                try

                                {

                                   DoSomeWork((string)args);

                                }

                                finally

                                {

                                    //worker ended

                                    signalEv.Decrement();

                                }

                            }, s );

                    }

                    signalEv.Decrement();

                signalEv.Wait();

            }

        }
Leave a Comment
  • Please add 7 and 5 and type the answer here:
  • Post
  • PingBack from http://blog.a-foton.ru/2008/06/21/forkjoin-parallelism-with-net-countdownevent/

  • PingBack from http://blog.cwa.me.uk/2008/06/23/the-morning-brew-120/

  • GPU support ?  Whats the roadmap for combined multi-core and GPU stream processors for the Parallel FX?  

    I want to make use of every processing unit I have available on my hardware substrates.

  • Very similar code can be written using the Semaphore class. Is there any particular reason for using CountDownEvent instead of Semaphore?

    Regards,

  • Semaphore (or SemaphoreSlim) provides, in a sense, the inverse of a CountdownEvent.  With CountdownEvent, signaling it N times causes it to become set such that waiting on it will succeed.  With Semaphore, waiting on it N times causes it to (in effect) be unset, such that waiting on it will block.  It's not an exact comparison, but it's one way to think of the two.  

    In general, I'd use a CountdownEvent when I need some threads to wait for a set of operations to complete.  And I'd use a Semaphore when I want to throttle threads' usage of a resource or their access to particular regions of code.

  • From documentation (Vs2010 CTP), I got the impression that CountdownEvent is a single usage object. Once the count goes to zero, and thread is awakened, you have to pretty much it throw away. For next fork/join operation one has to allocate it again. It would have been nice if we can re-use object and do not have to allocate it again and again because it causes allocation in kernel.

    I guess one can use Barrier Class to get similar behavior and it is a reusable object. (When a barrier call is returned, one thread can set another event… too expensive and clunky?) May be CLR is/will be smart enough to re-use kernel object internally and at that point this is not longer an issue.

    CountDownEvent<AutoResetEvent>, CountDownEvent<ManualResetEvent>?

Page 1 of 1 (6 items)