Thread-safe data structures .NET 4.0 (part 1)

.NET 4 contains rich set of tools, that allow to create parallelized code more easy. But when we start processing chunk of data in parallel threads, we need to synchronize these threads, and we need some storage for the results of work. Now exist a big number of methods, solving sync issues, and we can realize in code any of them. But MS Parallel Extensions team already do it, and .NET 4 beta versions include set of thread-safe data structures. They implement some popular types of collections, and I want to do an overview of them.

1. Queue: ConcurrentQueue

This is a classic FIFO queue, which can be safely accessed from few threads simultaneously. API of this collection is very simple:

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

queue.Enqueue(10);

int t;

Console.WriteLine(queue.TryPeek(out t));

Console.WriteLine(queue.TryDequeue(out t));

TryPeek() and TryDequeue() methods return an element from queue, but TryPeek() doesn’t remove it from queue. Both methods returns true, if they take an element, else they return false. You can add an element by calling Enqueue() method. Count of elements in the queue you can get by Count property, and if collection contains at least 1 element, property IsEmpty returns true.

2. Stack: ConcurrentStack

This is a simple LIFO stack, that can be used in the concurrent environment. In contrast to queue, you can easily add or take the range of elements:

ConcurrentStack<int> stack = new ConcurrentStack<int>();

stack.Push(10);

stack.PushRange(new int[] { 1, 2, 3, 4, 5 });

int t;

if (stack.TryPop(out t))

{

Console.WriteLine("Pop: " + t);

}

if (stack.TryPeek(out t))

{

Console.WriteLine("Peek: " + t);

}

int[] ts = new int[5];

int count;

if ((count = stack.TryPopRange(ts, 0, 3)) > 0)

{

Console.WriteLine("PopRange");

for (int i = 0; i < count; i++)

{

    Console.WriteLine(ts\[i\]);

}

}

Here is the result of work of this code:

image

TryPeek() and TryPop() methods return bool values, and TryPopRange() – count of retrieved elements. You may add range of values by calling PushRange() method.

3. Collection: ConcurrentBag

This is an unordered collection of data, which is looks like a set, but it can store duplicate items. It doesn’t guarantee any order of elements. I think, it’s a most simple data structure:

ConcurrentBag<int> bag = new ConcurrentBag<int>(new int[] { 1, 1, 2, 3 });

bag.Add(70);

int t;

bag.TryPeek(out t);

Console.WriteLine(t);

bag.Add(110);

Console.WriteLine();

for (int i = 0; i < 3; i++)

{

bag.TryTake(out t);

Console.WriteLine(t);

}

This piece of code produce following console output:

image

Looking at screenshot, you can notice, that elements retrieved in LIFO. I repeat: no any orders is guaranteed.

Take a look at the constructor of bag – you can set initial range of elements, and constructors of queue and stack have appropriate overloads too.

4. Dictionary: ConcurrentDictionary<TKey, TValue>

It’s just a variant of Dictionary<TKey, TValue>, which can be accessed concurrently. API for this collection has some thread-safe specific features:

ConcurrentDictionary<string, string> dict = new ConcurrentDictionary<string, string>();

dict.TryAdd("name", "OFC340");

dict.TryAdd("age", "25");

dict.TryAdd("age", "25");

Create/update/delete actions can be executed by calling one of Try* methods, which return true on success, else false. In the code sample, first attempt to add value “25” with key “age” is ok, and TryAdd return true. Second attempt is fail, and TryAdd() return false, and exception will not be generated. Other example:

string t = string.Empty;

Console.WriteLine(dict.TryGetValue("nokey", out t));

Key “nokey” is not present in the dictionary, but TryGetValue() method doesn’t generate any exception, and only return false. You can remove an element as shown here:

Console.WriteLine(dict.TryRemove("age", out t));

Using properties Values and Keys you can receive actual on the moment of call collections of values and keys of dictionary.

So, you can see, that these data structures is very basic, and don’t contains tons of functions – only necessary things. I like this concept – it’s a base structures and too many functions is redundant. If you need something else, you can easily add it at any time.

In addition to described data structures, .NET 4 beta 1 contains list ConcurrentLinkedList. But I don’t cover it, because MSDN contains following warning: «ConcurrentLinkedList(of T) is planned to be removed prior to the final release of Visual Studio 2010. Please do not use this class». And in the .NET 4 beta 2 this collection is removed.

Ok, I show you 4 basic thread-safe collections of data. In the next part of the article I show you BlockingCollection – more complex and interest thread-safe storage. Stay tuned! :)

This article is part of the GWB Archives. Original Author: Vitus

New on Geeks with Blogs

  • We Won The One Award I Actually Care About

    Full Scale made the Inc. 5000 for the fifth year straight, the 12th listing across my three companies. Here is why the one award you cannot buy is worth stopping for.

  • Your Customers Build the Features Now

    I let a tool I liked sit dead for a year rather than build the features I wanted. An MCP server meant I never had to, and your customers can do the same to your product.

  • Get the Size of a Directory in Linux the Easy Way

    du -sh for the quick answer, ncdu for the cleanup, df for the disk itself: every command for checking directory size in Linux, plus why du and df never agree.

  • Vim Search and Replace: The Ultimate Guide

    One :%s command replaces every match in a file before a find dialog would even open. The Vim substitute patterns worth the muscle memory: flags, ranges, capture groups, and multi-file edits.