This code demonstrates the ability to potentially choose your processor for which your .Net code’s thread can run on. I say that potentially, because the system at anytime can swap out the thread to the other CPU(s).

Background

I had seen an application provide the ability to specify a CPU’s processor. That application was the old Seti@AtHome PC console application used to find alien signals from the galaxy.

Using that CPU setting, I would set it up to run on Xeon machines with four processors and have a separate seti work unit run on each of the four CPU’s. (Doing tricks like that I was able to process 30,000 seti units over the span of six years which equated to 28 years of computer time. Look for OmegaMan in the fifth position). But I digress…

So I knew that this could be done, at least in win32 code, and I had seen MSDN forum posts, where questions would arise,  

“Can I specify a CPU in my .Net app if I have a multi-processor machine?”

Now for those in the know the reason to attach to a cpu doesn’t really buy one much and its best to leave the thread CPU allocation to the OS…but that doesn’t mean one can’t do it.

My Example Application

The following C# code will spike each CPU in unison, if the machine is a multi-processing machine (yes single core hyper threaded machines will work). The code runs a thread on a target CPU with a task of figuring out PI to 750 digits. (See my article entitled Calculate PI using C# which gives the class which will do the calculation. This code is not shown in this article) which spikes the target CPU. I could have done other busy waiting…but PI is somehow, more noble and seemed to be an appropriate task. Here is what it looks like when run. Notice that the primary core spiked first followed by the next. (Picture taken on a Hyper-Threaded machine, so the second is the logical processor).

image

Code

Now to achieve this magic we are going to set the way back machine to include some System 32 functions and threading. Here are the required usings:

using System.Threading;
using System.Runtime.InteropServices;

within our class we will specify the Kernal 32’s methods we are interested in

[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentThread();
[DllImport("kernel32.dll")]
static extern IntPtr SetThreadAffinityMask(IntPtr hThread, IntPtr dwThreadAffinityMask);

Within the thread here is the code to do the magic:

   1: try
   2: {
   3:     int processor = (int)target;
   4:     Thread tr = Thread.CurrentThread;
   5:  
   6:     if (Environment.ProcessorCount > 1)
   7:     {
   8:         SetThreadAffinityMask(GetCurrentThread(),
   9:             new IntPtr(1 << processor));
  10:     }
  11:  
  12:     CalculatePI.Process(PiSignificantDigits);
  13: }
  14: catch (Exception ex)
  15: {
  16:     _ex = ex;
  17: }

 

  • Line 03:  The target is a zero based number to specify the processor in sequence.
  • Line 08:  This function will specify which CPU to run on. It works with the Affinity Mask which we will set.
  • Line 09:  We shift the mask over to show which processor we want.
  • Line 12:  Here is where we do the work on hopefully the target cpu.

That should do it!

We have specified the thread to run on the target CPU. Here is the full code for your perusal.

Full Code

Note to use the code, call the static function on the class Usage() which will do/demonstrate how to use the class. Also the Calculation of Pi can be found here (Calculate PI using C#), otherwise you will need to do busy work which will spike the CPU.

using System.Threading;
using System.Runtime.InteropServices;
// Target a specific processor for the thread to run on
public class ThreadProcessor
{
    [DllImport("kernel32.dll")]
    static extern IntPtr GetCurrentThread();
    [DllImport("kernel32.dll")]
    static extern IntPtr SetThreadAffinityMask(IntPtr hThread, IntPtr dwThreadAffinityMask);
 
    public static void Usage()
    {
        int cpu = 0;
        ThreadProcessor tp = new ThreadProcessor();
        Console.WriteLine("Spike CPU 1");
        tp.SpikeCPU(cpu);
 
        if (tp._ex != null)
        {
            Console.WriteLine(tp._ex.Message);
        }
        else
        {
 
            if (Environment.ProcessorCount > 1)
            {
                while (++cpu < Environment.ProcessorCount)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine("Spike CPU " + (cpu + 1).ToString());
                    tp.SpikeCPU(cpu);
 
                    if (tp._ex != null)
                    {
                        Console.WriteLine(tp._ex.Message);
                        break;
                    }
                }
 
            }
            else // Either a single CPU or hyperthreading not enabled in the OS or the BIOS.
            {
                Console.WriteLine("This PC does not have two processors available.");
            }
        }
 
    }
 
    private Thread _worker;
    private const int PiSignificantDigits = 750; // Adjust to your processor
 
    // Spike the CPU and waith for it to finish
    public void SpikeCPU(int targetCPU)
    {
 
        // Create a worker thread for the work.
        _worker = new Thread(DoBusyWork);
 
        // Background is set so not to not prevent the
        // mainprocess from terminating if someone closes it.
        _worker.IsBackground = true;
 
        _worker.Start((object)targetCPU);
        _worker.Join(); // Wait for it to be done.
    }
 
 
    public void DoBusyWork(object target)
    {
        try
        {
            int processor = (int)target;
            Thread tr = Thread.CurrentThread;
 
            if (Environment.ProcessorCount > 1)
            {
                SetThreadAffinityMask(GetCurrentThread(),
                    new IntPtr(1 << processor));
            }
 
            CalculatePI.Process(PiSignificantDigits);
        }
        catch (Exception ex)
        {
            _ex = ex;
        }
 
    }
 
    public Exception _ex = null;
 
 
}
Share