State Management : Native Parallel Programming for Visual C++

At TechEd 2009, Microsoft announced the release of Beta 1 of Visual Studio 2010. This release contains a number of new features in the Parallel Programming Library (PPL) over the October 2008 CTP. In addition to some changes to task groups (covered in-depth previously), Visual Studio 2010 Beta 1 includes new state management functionality that simplifies multi-task development.

Task Group Cancellation

One of the new Beta 1 PPL features is the ability to cancel a running task group. Joining the run and wait method on the task_group type is a new cancel method. There is also a corresponding is_canceling method that you can use to check whether a cancelation is in progress. The task_group_status enumeration also has a new value called canceled that lets you check whether cancelation occurred. The following code demonstrates these new features:

Combinable

One of the most effective patterns to achieve the maximum benefit from executing programming tasks in parallel is for each parallel branch to work on a local copy or subset of the data being processed, and then combine the results when processing completes. This pattern minimizes resource contention, and eliminates the potential for deadlock and data inconsistency bugs that can occur when parallel threads attempt to update the same memory location.

There is nothing overly complex about using this pattern, but it can be tedious to code it manually for each use. To simplify pattern usage, Beta 1 of Visual C++ 2010 adds the combinable templated type. The template parameter passed to combinable is the type of the object that each task will operate on. The type must have both a default constructor and a copy constructor. Each task accesses its own copy of the combinable managed resource using the local method. After all tasks are complete, you can combine the results into a single result set using either the combine or combine_each method.

The following code adds elements to a vector using three separate tasks, and then combines the results into a single vector using both combination methods.

Note the use of the explicit return type declaration in the lambda expression in the last code statement. The two statements inside the lambda expression prevent the compiler from correctly inferring the return type, so manual declaration is required.

It's possible to use combinable for types that do not have a default constructor (or in situations where the use of the default constructor is not appropriate) by using the combinable constructor, which takes a generator function that creates objects of the type of the template parameter. The first few lines of the preceding code sample are rewritten below using the overloaded generator constructor. In this case, the generator function returns a vector that already contains an element.

Synchronization

The use of combinable is dependent on one task not needing the results of processing on other tasks. In cases where multiple tasks need to share an object, you must use more traditional synchronization strategies. The PPL ships with three synchronization primitives - critical_section, reader_writer_lock and event. A critical_section locks a memory location against access for all tasks that do not hold the lock. For memory locations that will have many simultaneous readers and fewer writers, the more optimized reader_writer_lock is available, which allows multiple readers to acquire the lock and access the memory location concurrently. The final primitive is event, which is used to signal between tasks and threads.

The synchronization primitives are defined in the concrt.h header file-the base header file for the PPL (included in ppl.h). Most of the types defined in concrt.h are targeted more toward library authors rather than application developers, but anyone interested in deeper-level concurrency development is free to investigate and use appropriate features from the exposed types.

The API of the critical_section type is extremely simple; you use a blocking lock method to acquire the lock, a non-blocking try_lock is to attempt to acquire the lock if it is available, and unlock to release a locked critical_section.

The reader_writer_lock is only marginally more complex. It adds two methods, lock_read and try_lock_read that support acquiring a reader lock. The unlock method remains the same as critical_section, and will release the appropriate lock based on the type of lock that is being held.

The final synchronization primitive is event, which represents a manual reset event (i.e. the event stays set until it is manually reset by external code). Code can wait for a single event to be set by calling the instance wait method, which also supports an optional timeout value. When no timeout is specified, the wait time is infinite. You can wait on multiple events by using the static wait_for_multiple, which accepts a C-style array of event pointers. The wait_for_multiple method waits for either a single event or for all the events passed into the method call. The code below waits for both events to be set:

Dealing with state management when executing tasks concurrently is a notoriously difficult undertaking. The PPL provides support for a pattern of state management where each thread operates on a local version of the shared object, with the results combined together at the completion of processing. For scenarios where segregated state management is not appropriate, the PPL provides traditional synchronization primitives in the form of critical sections, reader writer locks and events.

About the Author

Nick Wienholt is an independent Windows and .NET consultant based in Sydney. He is the author of Maximizing .NET Performance and co-author of A Programmer's Introduction to C# 2.0, from Apress, and specializes in system-level software architecture and development, with a particular focus on performance, security, interoperability, and debugging.

Nick is a keen and active participant in the .NET community. He is the co-founder of the Sydney Deep .NET User group and writes technical articles for the Australian Developer Journal, ZDNet, Pinnacle Publishing, CodeGuru, MSDN Magazine (Australia and New Zealand Edition) and the Microsoft Developer Network. In recognition of his work in the .NET area, Nick was awarded the Microsoft Most Valued Professional Award from 2002 through 2007.

Comments

There are no comments yet. Be the first to comment!

You must have javascript enabled in order to post comments.

Leave a Comment

Your email address will not be published. All fields are required.

Name

Email

Title

Comment

Top White Papers and Webcasts

U.S. companies are desperately trying to recruit and hire skilled software engineers and developers, but there is simply not enough quality talent to go around. Tiempo Development is a nearshore software development company. Our headquarters are in AZ, but we are a pioneer and leader in outsourcing to Mexico, based on our three software development centers there. We have a proven process and we are experts at providing our customers with powerful solutions. We transform ideas into reality.

When individual departments procure cloud service for their own use, they usually don't consider the hazardous organization-wide implications. Read this paper to learn best practices for setting up an internal, IT-based cloud brokerage function that service the entire organization. Find out how this approach enables you to retain top-down visibility and control of network security and manage the impact of cloud traffic on your WAN.