不要费力地这样做。
相反,使用线程池。线程池是框架的一种机制(实际上是一个类),您可以查询它以获取新线程。
当您请求一个新线程时,它会给您一个新线程或将工作排队直到有空闲线程。这样,框架负责决定是否应该根据当前CPU数量创建更多线程。
编辑:此外,正如已经提到的那样,操作系统负责在不同的CPU之间分配线程。
使用线程池并不一定简单。
默认情况下,线程池为每个CPU分配多个线程。由于参与你所做工作的每个线程都有成本(任务切换开销、使用CPU非常有限的L1、L2甚至L3缓存等),因此使用的最佳线程数<=可用CPU数量,除非每个线程都在请求其他机器提供的服务-例如高度可伸缩的Web服务。对于某些情况,尤其是涉及更多硬盘读写而非CPU活动的情况,一个线程可能比多个线程更好。
对于大多数应用程序,特别是WAV和MP3编码,应将工作线程数限制为可用CPU数。以下是一些C#代码,用于查找CPU数量:
int processors = 1;
string processorsStr = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
if (processorsStr != null)
processors = int.Parse(processorsStr);
很遗憾,仅仅限制CPU核数是不够的。您还需要考虑硬盘控制器和磁盘的性能。
找到最优线程数量的唯一方法是通过试验和错误。当使用硬盘、Web服务等时,这一点尤为真实。对于硬盘而言,您可能最好不要使用四个处理器内所有的处理器。另一方面,在某些Web服务中,每个CPU进行10甚至100个请求可能更好。
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
namespace Esquenta
{
class Program
{
private static int numThreads = 1;
static bool affinity = false;
static void Main(string[] args)
{
if (args.Contains("-a"))
{
affinity = true;
}
if (args.Length < 1 || !int.TryParse(args[0], out numThreads))
{
numThreads = 1;
}
Console.WriteLine("numThreads:" + numThreads);
for (int j = 0; j < numThreads; j++)
{
var param = new ParameterizedThreadStart(EsquentaP);
var thread = new Thread(param);
thread.Start(j);
}
}
static void EsquentaP(object numero_obj)
{
int i = 0;
DateTime ultimo = DateTime.Now;
if(affinity)
{
Thread.BeginThreadAffinity();
CurrentThread.ProcessorAffinity = new IntPtr(1);
}
try
{
while (true)
{
i++;
if (i == int.MaxValue)
{
i = 0;
var lps = int.MaxValue / (DateTime.Now - ultimo).TotalSeconds / 1000000;
Console.WriteLine("Thread " + numero_obj + " " + lps.ToString("0.000") + " M loops/s");
ultimo = DateTime.Now;
}
}
}
finally
{
Thread.EndThreadAffinity();
}
}
[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();
[DllImport("kernel32.dll")]
public static extern int GetCurrentProcessorNumber();
private static ProcessThread CurrentThread
{
get
{
int id = GetCurrentThreadId();
return Process.GetCurrentProcess().Threads.Cast<ProcessThread>().Single(x => x.Id == id);
}
}
}
}
结果如下:
处理器速度(由任务管理器显示),与CPU-Z报告的相似:
#define MAX_CORE 256
processor_mask[MAX_CORE] = {0};
core_number = 0;
Call GetLogicalProcessorInformation();
// From Here we calculate the core_number and also we populate the process_mask[] array
// which would be used later on to set to run different threads on different CORES.
for(j = 0; j < THREAD_POOL_SIZE; j++)
Call SetThreadAffinityMask(hThread[j],processor_mask[j]);
//hThread is the array of handles of thread.
//Now if your number of threads are higher than the actual number of cores,
// you can use reset the counters(j) once you reach to the "core_number".
Thread1-> Core1
Thread2-> Core2
Thread3-> Core3
Thread4-> Core4
Thread5-> Core5
Thread6-> Core6
Thread7-> Core7
Thread8-> Core8
Thread9-> Core1
Thread10-> Core2
...............
更多信息,请参考手册/MSDN以了解这些概念。
您不需要自己担心这个问题。我有在双四核机器上运行的多线程.NET应用程序,无论是通过线程池还是手动启动线程,我都可以看到工作在所有核心上的良好均匀分布。
通常情况下,每个线程运行在哪个核心上由操作系统自己处理...因此,在一个四核系统上生成四个线程时,操作系统将决定在哪些核心上运行每个线程,通常是每个核心上运行一个线程。
操作系统的工作是将线程分配到不同的核心上,当您的线程使用大量CPU时间时,它会自动执行。不用担心这个问题。至于如何找出用户有多少个核心,请尝试在C#中使用Environment.ProcessorCount
。
您不能这样做,因为只有操作系统才有特权执行此操作。如果您决定这样做......那么编写应用程序将变得困难。因为您还需要关注处理器间通信、临界区等问题。对于每个应用程序,您都需要创建自己的信号量或互斥锁......而操作系统会通过自身来提供一个通用解决方案。
不要(正如所说的)尝试自己分配这种类型的东西的原因之一是,您没有足够的信息来正确地执行它,特别是在 NUMA 等未来的情况下。
如果您有一个准备好运行的线程,并且有一个空闲的内核,内核将运行您的线程,不必担心。