Click here to Skip to main content
Click here to Skip to main content

OpenMP style multi-threading in C#

, 17 Sep 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
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:

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...

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.

     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)

Share

About the Author

adamvanner
Other Lecturer, Researcher, Software Developer, Animator
United Kingdom United Kingdom
Adam Vanner has worked at the National Centre for Computer Animation at Bournemouth University in the UK since 2000.
 
Specialising in 3D animation software development, 3D animation productions and motion capture, he has worked on a wide range of projects from design and development of commercial animation software, to collaborating with artists on animation for artworks and theatre.

Comments and Discussions

 
GeneralRe: Thread safety Pinmemberadamvanner8-Oct-08 13:45 
GeneralRe: Thread safety Pinmemberpopart8-Oct-08 21:06 
GeneralThe sources contain the bug!!! Pinmemberqweqrsfeg6hdfm5wufd23-Sep-08 3:52 
GeneralRe: The sources contain the bug!!! [modified] Pinmemberadamvanner8-Oct-08 13:35 
Hi qweqrsfeg6hdfm5wufd,
 
Actually, from looking at this a few times ( I had already posted in this very message saying all is fine) I can see what you mean- essentially threadDone is set to true, when the thread hasnt done. The strange thing is that changing it doesnt seem to improve or reduce the results. I guess its only coincidence that in my tests all results have been good- I can only presume that any final thread has completed in the time that the Dispose method has completed, and thus provided no obvious problem.
 
Please replace WaitToFinish() with the following:
 

 
public void WaitToFinish()
{
	if (disposed) return;
 
	bool done = false;
	int nThread = Threads.Length;
	while (!done)
	{
		done = true;
		for (int i = 0; i < nThread; i++)
		if (!threadDone[i])
		{
			if (Threads[i].IsAlive)
			{
				done = false;
				break;
			}
			else
			threadDone[i] = true;
		}
		Thread.Sleep(4);
	}	
}
 
As has been mentioned in another post, varying the sleep time can yield better results.
I also noticed on looking again that there is a howling school-boy error in that the Sleep call is outside the while loop! doh.
 
Thanks for pointing this out and happy threading Big Grin | :-D
 
modified on Friday, October 10, 2008 6:22 PM

QuestionMS Parallel Extensions library PinmemberNick Butler22-Sep-08 23:46 
AnswerRe: MS Parallel Extensions library Pinmemberadamvanner8-Oct-08 13:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.141029.1 | Last Updated 17 Sep 2008
Article Copyright 2008 by adamvanner
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid