ReBuildAll
LenardG's thoughts (mostly) on .NET development

Running .NET threads on selected processor cores   (Framework)   
Today multi-threading is very popular. Just look at the upcoming .NET 4, with parallel programming built right into the framework. It is very easy to run a for or foreach loop that will utilize all cores in the machines - 2, 4, 8 -, and thus increasing performance.

But what if you want to restrict a thread to a given core yourself?

Process affinity

For the entire process, you can adjust the affinity - that is, the processor the process is allowed to run on - by using the following .NET code (C#):

            Process.GetCurrentProcess ().ProcessorAffinity = new IntPtr ( 2 );

This will restrict the current process to running on processor #2 (if we begin numbering with 1). The passed IntPtr is a bit mask, where the first bit means the first processor core, the second bit the second processor core, and so on. To run the process on all cores on a dual core system, you would use 3. On a quad core machine you would use 15.

The same scheme is in use if you have multiple processors. In that case starting from the first bit comes the cores for the first processor, then for the second processor, and so on. For a dual cpu system with dual cores, you would use 3 for the first cpu (both cores) and 12 for the second cpu (both cores).

Thread affinity

For threads, this is not so easy to acomplish. First of all, a .NET thread does not correspond to an operating system thread. And you can set thread affinity for OS threads only. Not only is there no correspondance, but the .NET Framework is allowed to run your .NET thread on multiple operating system threads. Not at the same time, but should your thread run long enough (waiting in between, etc), there is no guarantee that it will always run on the same OS thread.

To solve the problem of the CLR running .NET threads on multiple OS threads, you can use a method from the Thread class:

Thread.BeginThreadAffinity ();
...
Thread.EndThreadAffinity ();

This will guarantee that any code between these calls will always run on the same OS thread. Essentially this disables parts of the CLR thread management.

After we have this problem solved, we can get on with the thread processor affinity issue. You can get the OS threads in your .NET application by using Process.GetCurrentProcess ().Threads. This is a collection of thread objects. However, these are using OS thread IDs and not managed thread IDs. To get the currently executing OS thread, we can use P/Invoke to invoke the neccessary Win32 API code:

        [DllImport ( "kernel32.dll" )]
        public static extern int GetCurrentThreadId ();

With the returned ID we can find our thread, and the .NET ProcessThread object has a property called ProcessorAffinity. This property only has a setter, so you cannot get its value. The actual property works similar to the process affinity I described above.

Putting it all together

Now that we have pieces of the puzzle, it is time to put it together. Below you will find a complete class which I called DistributedThread that allows you to run threads on processor cores you can determine.

Before you start hard coding processor and core numbers, make sure you retrieve the available cores in the system (including all CPUs and cores) with Environment.ProcessorCount.

The code encapsulates the normal Thread object. It handles restricting the thread to run on the current OS thread and then setting the thread affinity to the desired value.

(if you wonder how you can get the code without the nice syntax highlighting, just request the page source, you will find it there ... a little challenge for you :P)

using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;

namespace DistributedWorkManager
{
    public class DistributedThread
    {
        [DllImport ( "kernel32.dll" )]
        public static extern int GetCurrentThreadId ();

        [DllImport ( "kernel32.dll" )]
        public static extern int GetCurrentProcessorNumber ();

        private ThreadStart threadStart;

        private ParameterizedThreadStart parameterizedThreadStart;

        private Thread thread;

        public int ProcessorAffinity { get; set; }

        public Thread ManagedThread
        {
            get
            {
                return thread;
            }
        }

        private DistributedThread ()
        {
            thread = new Thread ( DistributedThreadStart );
        }

        public DistributedThread ( ThreadStart threadStart )
            : this ()
        {
            this.threadStart = threadStart;
        }

        public DistributedThread ( ParameterizedThreadStart threadStart )
            : this ()
        {
            this.parameterizedThreadStart = threadStart;
        }

        public void Start ()
        {
            if ( this.threadStart == null ) throw new InvalidOperationException ();

            thread.Start ( null );
        }

        public void Start ( object parameter )
        {
            if ( this.parameterizedThreadStart == null ) throw new InvalidOperationException ();

            thread.Start ( parameter );
        }

        private void DistributedThreadStart ( object parameter )
        {
            try
            {
                // fix to OS thread
                Thread.BeginThreadAffinity ();

                // set affinity
                if ( ProcessorAffinity != 0 )
                {
                    CurrentThread.ProcessorAffinity = new IntPtr ( ProcessorAffinity );
                }

                // call real thread
                if ( this.threadStart != null )
                {
                    this.threadStart ();
                }
                else if ( this.parameterizedThreadStart != null )
                {
                    this.parameterizedThreadStart ( parameter );
                }
                else
                {
                    throw new InvalidOperationException ();
                }
            }
            finally
            {
                // reset affinity
                CurrentThread.ProcessorAffinity = new IntPtr ( 0xFFFF );
                Thread.EndThreadAffinity ();
            }
        }

        private ProcessThread CurrentThread
        {
            get
            {
                int id = GetCurrentThreadId ();
                return
                    (from ProcessThread th in Process.GetCurrentProcess ().Threads
                     where th.Id == id
                     select th).Single ();
            }
        }
    }
}


How you can use this code?

   DistributedThread thread = new DistributedThread( ThreadProc );
   thread.ProcessorAffinity = 2;
   thread.ManagedThread.Name = "ThreadOnCPU2";
   thread.Start ();

As you can see the syntax is fairly similar to when you use Thread. The ManagedThread property gives access to the actual Thread object, should you need that. The affinity here is a single int value - the class handles converting that to IntPtr.

 

Comments

Weimin Xiao Re: Running .NET threads on selected processor cores
The code works. I used it to make a parallel loop implementation, and saw CPUs can be clear targeted using the DistributedThread class.

The only thing to pay a bit attention is that ProcessorAffinity assignment for CPU 0, 1, 2, 3, ..., N is 1, 2, 4, 8, ... 2^N.
Lenard Gunda Re: Running .NET threads on selected processor cores
Jannik, automatic properties have been around since C# 3.0 (.NET 3.0). I did not really point out which version of the code was written for. If you are using an earlier version of the .NET Framework (1.1 or 2.0), you will need to define a backing field for the property as follows:

private int _processorAffinity;

public int ProcessorAffinity
{
   get { return this._processorAffinity; }
   set { this._processorAffinity = value; }
}

Jannik Re: Running .NET threads on selected processor cores
what about the

public int ProcessorAffinity { get; set; }

must declare a body because it is not marked abstract or extern