Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

OpenMP style multi-threading in C#

4.27/5 (11 votes)
17 Sep 2008CPOL4 min read 1   1.1K  
Simplified multi-threading of for loops in C#, with an interface akin to OpenMP.

Introduction

This article demonstrates the use of C# anonymous delegates and a using() statement to achieve a very simple interface to multi-threading, in a manner in akin to OpenMP in C++.

It also provides an implementation and example of a multi-threading object, avForThread.

Background

Threading, in any language, introduces problems at all levels, from design, to implementation, to debugging. The OpenMP extension to Visual C++ allows programmers either to quickly parallelise otherwise sequential functions, or at least to test whether more specialised threading routines will be beneficial for their task. Unfortunately, currently, C# has no OpenMP extension implementation (at the time of writing, at least!)

However, C# does have several very elegant toys which can be combined to produce a coding interface very similar to that of OpenMP; primarily anonymous delegates, and using().

Anonymous delegates sport two great properties. One, they can be casually defined within function definitions; two, objects defined outside of the scope of the delegate, but within the defining scope, are still visible and accessible.

For example:

C#
public delegate void testSetter(int setTo); 
void myMethod()
{
  int b;
  testSetter D = delegate(int setTo) { b= setTo; }

  D(123);
}

In this case, the delegate instance D is set to an anonymous delegate defined inline. The delegate, in this case, sets b to the value of the incoming parameter.

Whilst this syntax is evidently not useful in this case, it does allow the programmer a large amount of flexibility, especially when it comes to dynamic querying, or polymorphic behaviour without the need for the bloat of new classes.

One use where this syntax is very useful is in being able to wrap up functions with any number of parameters and have threads execute them.

For example, if we pass an anonymous delegate to the constructor of a System.Threading.Thread object, we can then run the delegate with the parameters of our choosing.

So, let's say we want to take a for loop and split it into a number of threads. (In fact, the provided code only handles for loops, but it could be easily extended to handle any sequential mutual calculations within any looping structure - just don't write to data which may be read or written to by another thread!) As we want to use the calculated data immediately, we will wait for the threads to return.

The code attached defines all tasks needed for this kind of operations within one class, avForThread.

To remove the need to call a method to wait for the completion of the threads, we can employ a trick with a using() statement such that the execution will not leave the using block until the threads have returned. This is achieved by the call to the thread wait method, avForThread.WaitToFinish(), stated within avForThread.Dispose().

Using the code

So, we will start by creating a test for loop, the kind of code that could be parallelised...

C#
static void Main(string[] args)
{
    // we want to call calculateSomething 1M times.
    int dataSize = 1000000;

    // we'll store the data in an array
    int[] data = new int[dataSize];

    // we'll do the same calculation twice,
    // once without thread, and another time with

    // without threading
    for (int i = 0; i < dataSize; i++)
    {
        data[i] = calculateSomething(i);
    }

We'll then move on to performing the same task using the threaded notation, using an avForThread object. We will need to pass the same start and end values used by our simple for loop. We will also pass the number of threads to use, and of course, the parameterised delegate to execute.

C#
     using (new avForThread(0, dataSize, 4,
        delegate(int start, int end)
        {
            for (int i = start; i < end; i++)
            {
                data[i] = calculateSomething(i);
            }
        })
    ) ;
}

N.B.: To change the looping range, we can modify the 0 or dataSize arguments. Only, we don't need to modify start or end - they will be automatically set per thread by avForThread.

As you can see, the syntax is very simple and flexible. You will need to test with differing numbers of threads to gain the best performance. Depending on the complexity of the task and the CPU, there will be a "sweet spot" where the threaded code is far faster than the non-threaded code. Experiment with numThreads in the range 2 to 20.

For your own implementations, you'll need to keep roughly the same format as the for loop has here. By the way, here, the using() simply forces the runtime to call the Dispose method of the class, which in turn will wait for the threads to finish.

You could also omit the using statement, carry on working, and then call the avForThread.WaitToFinish() method explicitly.

As I said, if you want to go beyond a simple i++ for loop, some simple customisation of avForThread will be required.

Have fun!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)