Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

ThreadPoolComponent: Multithreading Simplified

4.00/5 (3 votes)
9 Mar 2009CPOL2 min read 17.7K   311  
Provides a simple wrapper around the shared ThreadPool. It may be used to divide a task into a set of threads which are executed concurrently.

Introduction

I'd like to present my ThreadPoolComponent, a simplistic wrapper around the shared ThreadPool. It may be used to divide a task into a set of threads which are executed concurrently.

Using the Code  

  • Create an instance of the component, either by coding it yourself or by placing the component onto a control. 
  • Handle the ThreadStarted event. The event handler will be invoked by each thread when it is started and should perform the actual work. 
  • Optionally handle the ThreadFinished and AllThreadsFinished events in case you'd like to be notified. 
  • The component's default behavior is to bind every thread to a single CPU core. Adjust the FirstAffinityIndex property to change the behavior. 
  • Invoke one of the component's Start() overloads: 
    • Start(): Starts as many threads as available CPU cores
    • Start(int numThreads): Starts the specified number of threads
    • Start(IEnumerable<T> tags): Starts one thread per specified state object (these state objects providing thread-specific in- and/or output are passed to the threads in the ThreadEventArgs instance used by the ThreadStarted and ThreadFinished events)  

Here is the initialization code of the associated sample program:  

C#
var component = new ThreadPoolComponent();

component.ThreadStarted += new EventHandler<ThreadEventArgs>(component_ThreadStarted);
component.AllThreadsFinished += new EventHandler(component_AllThreadsFinished);

component.Start();

Putting the component onto a control simplifies this procedure further as you only need to double-click on the events to create the handlers. Speaking of which, here's the sample implementation:  

C#
void component_ThreadStarted(object sender, ThreadEventArgs e)
{
	// if you supplied state objects when starting the threads,
	// you may access the current thread's one using e.Tag
	Console.WriteLine(string.Format("Thread #{0} says hello.", e.ThreadIndex));
}

void component_AllThreadsFinished(object sender, EventArgs e)
{
	Console.WriteLine("---\nAll threads are finished.");
}

Points of Interest

The component makes use of the shared ThreadPool because it usually decreases threading overhead by recycling threads. The maximum number of threads may be increased because the component tries to execute all threads concurrently. Using more threads than CPU cores (returned by the static AvailableCpuCores property) is probably not a good idea.

Binding a thread to a specific core by setting its affinity usually makes sense because CPU cores usually have separate caches. Avoiding thread hopping across cores prevents caches from being refilled, resulting in higher performance. Don't worry, the thread affinities are reset when a thread finishes, so that other ThreadPool clients are not affected.

Keep in mind that ThreadPool threads are background threads, i.e. they do not keep a process alive if all foreground threads exit.

Lastly, you obviously still need to synchronize access to shared objects in the event handlers.

History

  • 9th March, 2009: Initial post

License

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