Parallel Computing in .NET 4.0 - Concurrent Collections


When working on Multi Threaded programs the most important point is to make sure that object that is accessed by multiple threads and operations performed on the object are thread safe. If not we need to protect the object with proper locks to avoid incorrect results and exceptions.

Introduction

.NET Framework 4.0 introduces new set of Types like concurrent classes, Synchronization primitives and types for lazy initialization. These are highly scalable and improve performance and can be used to perform thread safe operations in any multi threaded programs including Parallel Task Library and PLINQ.

This article is continuation of Parallel Computing in .NET 4.0 – Task Parallel Library & PLINQ and Parallel Computing in .NET 4.0 - Parallel LINQ

For example, in order to add or remove items from a collection declared outside the parallel foreach loop we need to employ lock on it to make it as thread safe operation.

This can be avoided by using concurrent collection classes like ConcurrentBag, BlockingCollection etc. These classes present in System.Collections.Concurrent namespace and provide thread safe add or remove operations that avoids locks wherever possible and uses fine-grained locks wherever necessary.

Example:

// Code sample that uses a Queue and a lock:

Queue<int> q = new Queue<int>();

object myLock = new object();

Task.StartNew( () => for(int i = 0; i < 100; i ) {

int result = ComputeSomething(i);

lock(myLock) {

q.Add(result);

}});

// Code to use a ConcurrentQueue instead:

ConcurrentQueue<int> q = new ConcurrentQueue<int>();

Task.StartNew( () => for(int i = 0; i < 100; i ) {

q.Add(ComputeSomething(i));

});

Following are the classes available.

System.Collections.Concurrent.BlockingCollection<T>

A collection that is useful for implementing exchange buffers in producer-consumer scenarios. Provides blocking and bounding capabilities for thread-safe collections that implement System.Collections.Concurrent.IProducerConsumerCollection<T>. Producer threads block if no slots are available or if the collection is full. Consumer threads block if the collection is empty. This type also supports non-blocking access by consumers and producers. BlockingCollection<T> can be used as a base class or backing store to provide blocking and bounding for any collection class that supports IEnumerable<T>.

System.Collections.Concurrent.ConcurrentBag<T> - A thread-safe bag implementation that provides scalable add and get operations.

System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue> - Concurrent version of Dictionary

System.Collections.Concurrent.ConcurrentQueue<T> - Cuncurrent version of Queue. A concurrent and scalable FIFO queue.

System.Collections.Concurrent.ConcurrentStack<T> - Cuncurrent version of Stack. A concurrent and scalable LIFO stack.

Coordination Primitives

New coordination primitives in .NET 4 encapsulate some of the most common parallel programming patterns.

Lazy type provides lightweight and thread safe lazy initialization.

 

For example, you can create a lazily allocated large buffer as follows: 

Lazy<byte[]> lazyBuffer = new Lazy<byte[]>( () => new byte[1 << 24]);

The buffer will be allocated on the first access to lazyBuffer.Value.

 ThreadLocal<> - lazily-initialized value on a per-thread basis, with each thread lazily-invoking the initialization function can be used when we need to track a field or a variable that contains an independent value for each thread. For static fields, you can simply use the ThreadStatic attribute, but ThreadLocal<> also works for instance fields and local variables.

 This example uses ThreadLocal<> to track how many elements in a PLINQ query have been processed by each thread

ThreadLocal<int> localCount = new
ThreadLocal<int>(() => 0); 

Enumerable.Range(0, 100).AsParallel().Select(x =>



localCount.Value = localCount.Value 1;

Console.WriteLine("{0} elements so far processed on thread {1}",localCount.Value, Thread.CurrentThread.ManagedThreadId);

Thread.Sleep(100);

return x;

}).ToArray();

 Minimize the number of accesses to the Value property of a ThreadLocal<> object, especially if accessing the thread-local value is the bottleneck of the operation. Reads and writes to Value are fairly cheap, but not as cheap as accesses to local variables. 

For example, the ThreadLocal<> sample above could use a local variable to reduce the number of accesses to the Value property from three to two. 

ManualResetEventSlim – New synchronization primitive similar to System.Threading.ManualResetEvent. ManualResetEventSlim is lighter-weight but can only be used for intra-process communication. For more information, see ManualResetEvent and ManualResetEventSlim.

 You will, however, need to use the non-slim ManualResetEvent and Semaphore types if you need to pass the corresponding WaitHandle to some API (e.g. WaitHandle.WaitAny or WaitHandle.WaitAll), or if you need the wait handles for cross-process synchronization.

Reference: MSDN


Related tags

Concurrent Collections,Parallel Programming, .NET Framework 4.0, Parallel Computing