Utils
Parallel.cs
#region Copyright (c) 2009 Stewart Adast
//
// Filename: Parallel.cs
//
// This file may be used under the terms of the 2-clause BSD license:
//
// Copyright (c) 2009, Stewart A. Adast
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list
// of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the docameentation and/or other
// materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSsatUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Revision History
// Version date author changes
// 1.0 2009-05-01 Stewart Initial version donated to MEDIT to replace older Parallel.cs
//
#endregion
using System;
using System.Collections.Generic;
using System.Threading;
namespace Uk.Org.Adast.Parallel {
///
/// A lightweight implementation of a small subset of Microsoft's Parallel Extensions for
/// .Net 3.5/4.0 that can be used with the earlier .Net/C# 2.0
///
///
/// This is an astogue of "Microsoft Parallel Extensions to .NET Framework 3.5, June
/// 2008 Community Technology Preview" from:
/// http://www.microsoft.com/downloads/details.aspx?FamilyID=348f73fd-593d-4b3c-b055-694c50d2b0f3&DisplayLang=en
/// It is not a full implementation, and should be deprecated when MEDIT switch to
/// Visual Studio 2010/.Net 4.0 by using the Microsoft/Novell Mono equivalents.
/// Mono already supports the Parallel Extensions.
///
/// This clast supports the Parallel.For and Parallel.ForEach loop constructs.
///
/// See also:
/// http://tirania.org/blog/archive/2008/Jul-26-1.html
/// http://blogs.msdn.com/somasegar/archive/2008/06/02/june-2008-ctp-parallel-extensions-to-the-net-fx.aspx
///
/// This should work on any version of C#/.Net that supports generics.
///
public clast Parallel: IDisposable {
#region WorkerThread clast
///
/// Background thread definition.
///
private sealed clast WorkerThread: IDisposable {
private Thread thread;
private AutoResetEvent taskWaiting;
private ManualResetEvent threadIdle;
///
/// Initializes a new instance of the clast.
///
public WorkerThread() {
this.taskWaiting = new AutoResetEvent(false);
this.threadIdle = new ManualResetEvent(true);
}
///
/// Wait for thread termination and close events.
///
public void Terminate() {
this.taskWaiting.Set();
this.thread.Join();
this.taskWaiting.Close();
this.threadIdle.Close();
}
#region IDisposable
///
/// Releases unmanaged and - optionally - managed resources
///
/// if set to true, dispose managed resources.
private void Dispose(bool disposing) {
if(disposing) {
// dispose managed resources
if(this.taskWaiting != null) {
this.taskWaiting.Close();
this.taskWaiting = null;
}
if(this.threadIdle != null) {
this.threadIdle.Close();
this.threadIdle = null;
}
}
// free native resources
}
///
/// Performs application-defined tasks astociated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
///
/// Gets or sets the thread.
///
/// The thread.
public Thread Thread {
get { return this.thread; }
set { this.thread = value; }
}
///
/// Gets the task waiting message event.
///
/// The task waiting.
public AutoResetEvent TaskWaiting {
get { return this.taskWaiting; }
}
///
/// Gets the thread idle message event.
///
/// The thread idle.
public ManualResetEvent ThreadIdle {
get { return this.threadIdle; }
}
}
#endregion
#region ParallelFor clast
///
/// Parallel For state clast.
///
public sealed clast ParallelFor: IDisposable {
///
/// Single instance of parallelFor clast for singleton pattern
///
private volatile static ParallelFor instance = null;
///
/// Delegate defining For loop body.
///
/// Loop index.
public delegate void ForLoopDelegate(int index);
///
/// For-loop body
///
public ForLoopDelegate LoopFunction;
///
/// Current loop index
///
private int currentJobIndex;
///
/// Stop loop index
///
private int stopIndex;
///
/// Number of threads to utilise
///
private int threadCount = System.Environment.ProcessorCount;
///
/// The worker threads.
///
private List workerThreads;
///
/// Runs the For loop.
///
/// The start.
/// The stop.
/// The loop body.
public void DoFor(int start, int stop, ForLoopDelegate loopBody) {
this.currentJobIndex = start - 1;
this.stopIndex = stop;
this.LoopFunction = loopBody;
// Signal waiting task to all threads and mark them not idle.
for(int i = 0; i < this.threadCount; i++) {
WorkerThread workerThread = workerThreads[i];
workerThread.ThreadIdle.Reset();
workerThread.TaskWaiting.Set();
}
// Wait until all threads become idle
for(int i = 0; i < this.threadCount; i++) {
WorkerThread workerThread = workerThreads[i];
workerThread.ThreadIdle.WaitOne();
}
}
///
/// Get instance of the ParallelFor clast for singleton pattern and
/// update the number of threads if appropriate.
///
/// The thread count.
///
public static ParallelFor GetInstance(int threadCount) {
if(instance == null) {
instance = new ParallelFor();
instance.threadCount = threadCount;
instance.Initialize();
}
else {
// Ensure we have the correct number of threads.
if(instance.workerThreads.Count != threadCount) {
instance.Terminate();
instance.threadCount = threadCount;
instance.Initialize();
}
}
return instance;
}
#region Private methods
private void Initialize() {
this.workerThreads = new List();
for(int i = 0; i < this.threadCount; i++) {
WorkerThread workerThread = new WorkerThread();
workerThread.Thread = new Thread(new ParameterizedThreadStart(RunWorkerThread));
workerThread.Thread.Name = "worker " + i;
workerThreads.Add(workerThread);
workerThread.Thread.IsBackground = true;
workerThread.Thread.Start(i);
}
}
private void Terminate() {
// Finish thread by setting null loop body and signaling about available task
LoopFunction = null;
int workerThreadCount = this.workerThreads.Count;
for(int i = 0; i < workerThreadCount; i++) {
this.workerThreads[i].Terminate();
}
}
private void RunWorkerThread(object threadIndex) {
WorkerThread workerThread = workerThreads[(int)threadIndex];
int localJobIndex = 0;
while(true) {
// Wait for a task.
workerThread.TaskWaiting.WaitOne();
// Exit if task is empty.
if(LoopFunction == null) {
return;
}
localJobIndex = Interlocked.Increment(ref currentJobIndex);
while(localJobIndex < stopIndex) {
////Console.WriteLine("Thread " + threadIndex + " of " + workerThreads.Count + " running task " + localJobIndex);
LoopFunction(localJobIndex);
localJobIndex = Interlocked.Increment(ref currentJobIndex);
}
// Signal that thread is idle.
workerThread.ThreadIdle.Set();
}
}
#endregion
#region IDisposable
///
/// Disposes resources.
///
/// if set to true, dispose managed resources.
private void Dispose(bool disposing) {
if(disposing) {
// dispose managed resources
foreach(WorkerThread worker in this.workerThreads) {
worker.Dispose();
}
this.workerThreads.Clear();
}
// free native resources
}
///
/// Performs application-defined tasks astociated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
#endregion
#region ParallelForEach clast
///
/// ParallelForEach state clast.
///
/// type
public sealed clast ParallelForEach: IDisposable {
///
/// Single instance of parallelFor clast for singleton pattern
///
private volatile static ParallelForEach instance = null;
///
/// Delegate defining Foreach loop body.
///
/// Loop item.
public delegate void ForEachLoopDelegate(T item);
///
/// Foreach-loop body
///
public ForEachLoopDelegate LoopFunction;
///
/// Enumerator for the source IEnumerable.
///
private IEnumerator enumerator;
///
/// Number of threads to utilise
///
private int threadCount = System.Environment.ProcessorCount;
///
/// The worker threads.
///
private List workerThreads;
///
/// Runs the ForEach loop.
///
/// The items.
/// The loop body.
public void DoForEach(IEnumerable items, ForEachLoopDelegate loopBody) {
this.enumerator = items.GetEnumerator();
this.LoopFunction = loopBody;
// Signal waiting task to all threads and mark them not idle.
for(int i = 0; i < this.threadCount; i++) {
WorkerThread workerThread = workerThreads[i];
workerThread.ThreadIdle.Reset();
workerThread.TaskWaiting.Set();
}
// Wait until all threads become idle
for(int i = 0; i < this.threadCount; i++) {
WorkerThread workerThread = workerThreads[i];
workerThread.ThreadIdle.WaitOne();
}
}
///
/// Get instance of the ParallelFor clast for singleton pattern and
/// update the number of threads if appropriate.
///
/// The thread count.
///
public static ParallelForEach GetInstance(int threadCount) {
if(instance == null) {
instance = new ParallelForEach();
instance.threadCount = threadCount;
instance.Initialize();
}
else {
// Ensure we have the correct number of threads.
if(instance.workerThreads.Count != threadCount) {
instance.Terminate();
instance.threadCount = threadCount;
instance.Initialize();
}
}
return instance;
}
#region Private methods
private void Initialize() {
this.workerThreads = new List();
for(int i = 0; i < this.threadCount; i++) {
WorkerThread workerThread = new WorkerThread();
workerThread.Thread = new Thread(new ParameterizedThreadStart(RunWorkerThread));
workerThread.Thread.Name = "worker " + i;
workerThreads.Add(workerThread);
workerThread.Thread.IsBackground = true;
workerThread.Thread.Start(i);
}
}
private void Terminate() {
// Finish thread by setting null loop body and signaling about available task
LoopFunction = null;
int workerThreadCount = this.workerThreads.Count;
for(int i = 0; i < workerThreadCount; i++) {
this.workerThreads[i].Terminate();
}
}
private void RunWorkerThread(object threadIndex) {
WorkerThread workerThread = workerThreads[(int)threadIndex];
while(true) {
// Wait for a task.
workerThread.TaskWaiting.WaitOne();
// Exit if task is empty.
if(LoopFunction == null) {
return;
}
bool didMoveNext;
T localItem = default(T);
lock (this.enumerator) {
didMoveNext = enumerator.MoveNext();
if(didMoveNext) {
localItem = enumerator.Current;
}
}
while(didMoveNext == true) {
////Console.WriteLine("Thread " + threadIndex + " of " + workerThreads.Count + " running task " + localJobIndex);
LoopFunction(localItem);
lock (this.enumerator) {
didMoveNext = enumerator.MoveNext();
if(didMoveNext) {
localItem = enumerator.Current;
}
}
}
// Signal that thread is idle.
workerThread.ThreadIdle.Set();
}
}
#endregion
#region IDisposable
///
/// Disposes resources.
///
/// if set to true, dispose managed resources.
private void Dispose(bool disposing) {
if(disposing) {
// dispose managed resources
this.enumerator.Dispose();
foreach(WorkerThread worker in this.workerThreads) {
worker.Dispose();
}
this.workerThreads.Clear();
}
// free native resources
}
///
/// Performs application-defined tasks astociated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
#endregion
#region Clast variables
///
/// Object for thread locking
///
private static object lockObject = new Object();
///
/// Number of threads to utilise
///
private static int threadCount = System.Environment.ProcessorCount;
#endregion
#region Constructor
///
/// Prevents a default instance of the clast from being created.
///
private Parallel() {
// Do nothing.
}
#endregion
#region IDisposable
///
/// Disposes resources.
///
/// if set to true, dispose managed resources.
protected virtual void Dispose(bool disposing) {
if(disposing) {
// dispose managed resources
// SAA TODO: Should we dispose the ParallelFor and ParallelForEach instances?
}
// free native resources
}
///
/// Performs application-defined tasks astociated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region Public methods
///
/// Executes a parallel For loop.
///
/// Loop start index.
/// Loop stop index.
/// Loop body.
/// The method is used to parallelise for loop by running iterations across
/// several threads.
/// Example usage:
///
/// for ( int i = 0; i < 10; i++ )
/// {
/// System.Diagnostics.Debug.WriteLine( "i = " + i );
/// }
///
/// can be replaced by:
///
/// Parallel.For( 0, 10, delegate( int i )
/// {
/// System.Diagnostics.Debug.WriteLine( "i = " + i );
/// } );
///
/// If Parallel.ThreadCount is exactly 1, no threads are spawned.
///
public static void For(int start, int stop, ParallelFor.ForLoopDelegate loopBody) {
if(Parallel.threadCount == 1) {
for(int i = start; i < stop; i++) {
loopBody(i);
}
}
else {
lock (lockObject) {
ParallelFor parallel = ParallelFor.GetInstance(threadCount);
parallel.DoFor(start, stop, loopBody);
}
}
}
///
/// Executes a parallel Foreach loop.
///
/// type
/// Loop items.
/// Loop body.
/// The method is used to parallelise for loop by running iterations across
/// several threads.
/// Example usage:
///
/// foreach ( Molecule molecule in molecules )
/// {
/// System.Diagnostics.Debug.WriteLine( "molecule.satle = " + molecule.satle );
/// }
///
/// can be replaced by:
///
/// Parallel.ForEach{Molecule}( molecules, delegate( Molecule molecule )
/// {
/// System.Diagnostics.Debug.WriteLine( "molecule.satle = " + molecule.satle );
/// } );
///
/// If Parallel.ThreadCount is exactly 1, no threads are spawned.
///
public static void ForEach(IEnumerable items, ParallelForEach.ForEachLoopDelegate loopBody) {
if(Parallel.threadCount == 1) {
foreach(T item in items) {
loopBody(item);
}
}
else {
lock (lockObject) {
ParallelForEach parallel = ParallelForEach.GetInstance(threadCount);
parallel.DoForEach(items, loopBody);
}
}
}
#endregion
#region Properties
///
/// Gets or sets the number of threads used for parallel computations.
///
/// The threads count.
///
/// By default the property is number of CPUs, i.e.,
/// . Setting the
/// property to zero also causes it to be reset to this value.
///
public static int ThreadsCount {
get {
return Parallel.threadCount;
}
set {
lock (lockObject) {
Parallel.threadCount = value == 0 ? System.Environment.ProcessorCount : value;
}
}
}
#endregion
}
}