Poor Man’s Parallel Task Dispatcher

Even though multi-core processors are almost ubiquitous nowadays, applications are slow to catch up. Of course, one could always re-write the applications in order to take the full advantages in a multi-core environment. But it is not an easy undertaking. For applications that performs rather repetitive tasks over a well defined set of data (e.g. image processing) it is relatively easy to utilize multiple cores with some simple modifications to the applications themselves.

For the type of applications that perform the same task over a large dataset for example,

for (int i = 0 ; i < num ; i++) 
{
    //lengthy tasks
}

we could easily break down the for loop evenly into chunks that fit into the number of processors given. And for each of the subsets we could create a new process to perform the desired tasks. For instance, if we were to execute the above for loop in parallel on a duo-core processor, we could re-write each of the loops like the following:

//core 1
for (int i = 0 ; i < num ; i+=2) 
{
    //lengthy tasks
}
//core 2
for (int i = 1 ; i < num ; i+=2) 
{
    //lengthy tasks
}

By executing the application in separate processes, we eliminated certain race conditions that must be properly handled in a multi-threaded process and thus it is far easier to execute multiple processes concurrently when there is no inter-process communications involved than to handle multi-processing within a process via multi-threading. In a multi-process scenario, the operating system handles the concurrency issue behind the scene whereas in the multi-threading case the application developer must ensure the code regions are thread safe.

The following C# code shows how to dispatch tasks among different processes using asynchronous method invocations. Each process being invoked can identify its order by the extra integer parameter (line 36) passed to it. Thus we only needed to modify the target application to take this extra parameter into account (for instance, in a quad-core system, the first process would process items 1,5,9… and the second process would run items 2,6,10, and so on)

using System;
using System.Diagnostics;
using System.Threading;

namespace MPDispatcher
{
    class Program
    {
        public delegate bool CmdDlg(string fn, string args);

        private bool RunCommand(string fn, string args)
        {
            bool r = true;
            Process p = new Process();

            p.StartInfo.FileName = fn;
            p.StartInfo.Arguments = args;

			Console.WriteLine(String.Format("Starting {0} with arguments {1}. Thread ID: {2}", fn, args, Thread.CurrentThread.ManagedThreadId));
            r = p.Start();
            p.WaitForExit();

            return r;
        }

		public bool DispatchCommand(int numOfThreads, string args) 
        {
            bool r = true;

            IAsyncResult[] ars = new IAsyncResult[numOfThreads];
            CmdDlg[] dlgs = new CmdDlg[numOfThreads];

            for (int i = 0; i < numOfThreads; i++)
            {
                dlgs[i] = new CmdDlg(RunCommand);
                ars[i] = dlgs[i].BeginInvoke(args, i.ToString(), null, null);
				Thread.Sleep(100);
            }

            bool finished = false;

			//wait till all processes are finished.
            while (!finished)
            {
                bool t = true;

                for (int i = 0; i < numOfThreads; i++)
                    t = t && ars[i].IsCompleted;   

                if (t) finished = true;

                Thread.Sleep(100);
            }

            for (int i = 0; i < numOfThreads; i++)
            {
                r = r && dlgs[i].EndInvoke(ars[i]);
            }

            return r;
        }

        static void Main(string[] args)
        {
            Program p = new Program();
            
			if (args.Length < 1 || args[0].ToUpper() == "-H") {
				Console.WriteLine("Usage: MPDispatcher {app name} {app args}");
			} else {
				int numThreads = 4; //use 4 threads
				string argStr = string.Empty;
			
				for (int i = 1 ; i < args.Length; i++) {
					argStr = String.Format("{0} {1}", argStr, args[i]);
				}
				
            	p.DispatchCommand(numThreads, argStr);
			}
        }
    }
}

The above code works under Windows and Linux (Mono).

Be Sociable, Share!

Leave a Reply