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