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
)
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.