假设我在C#中创建了一个计算成本高的程序,比如将WAV文件列表编码为MP3.通常我会一次编码一个文件,但是假设我想让程序计算出我拥有多少CPU核心并在每个核心上启动编码线程.因此,当我在四核CPU上运行程序时,程序会发现它是一个四核CPU,可以看出有四个内核可以使用,然后生成四个用于编码的线程,每个线程都独立运行中央处理器.我该怎么做?
如果核心分布在多个物理CPU上,这会有什么不同吗?如果我有一台带有两个四核CPU的机器,是否有任何特殊注意事项,或者两个芯片中的八个内核在Windows中是否相同?
不要那么麻烦.
而是使用线程池.线程池是框架的一种机制(实际上是一个类),您可以查询新线程.
当你要求一个新的线程时,它会给你一个新的线程或者将这个工作排队,直到一个线程被释放.通过这种方式,框架负责决定是否应该创建更多线程,具体取决于当前CPU的数量.
编辑:此外,正如已经提到的,操作系统负责在不同的CPU之间分配线程.
它不一定像使用线程池那么简单.
默认情况下,线程池为每个CPU分配多个线程.由于参与您正在进行的工作的每个线程都有成本(任务切换开销,使用CPU非常有限的L1,L2和L3缓存等等),因此使用的最佳线程数<=可用CPU的数量 - 除非每个线程都从其他机器请求服务 - 例如高度可扩展的Web服务.在某些情况下,尤其是那些涉及比CPU活动更多硬盘读写的硬盘,实际上可以使用1个线程而不是多个线程.
对于大多数应用程序,当然还有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服务等时尤其如此.对于硬盘,最好不要在四处理器CPU上使用所有四个处理器.另一方面,对于某些Web服务,您最好每个CPU发出10个甚至100个请求.
在托管线程的情况下,执行此操作的复杂程度大于本机线程的复杂程度.这是因为CLR线程不直接绑定到本机OS线程.换句话说,CLR可以在其认为合适时将托管线程从本机线程切换到本机线程.提供函数Thread.BeginThreadAffinity以将托管线程与本机OS线程锁定在一起.此时,您可以尝试使用本机API来提供底层本机线程处理器关联.正如大家在这里所说,这不是一个好主意.事实上,有文档表明如果线程仅限于单个处理器或核心,则线程可以获得更少的处理时间.
您还可以浏览System.Diagnostics.Process类.在那里,您可以找到一个函数来枚举进程'线程作为ProcessThread对象的集合.这个类有设置ProcessorAffinity甚至设置首选处理器的方法 - 不知道那是什么.
免责声明:我遇到了一个类似的问题,我认为CPU的使用不足并研究了很多这样的东西; 然而,根据我读到的所有内容,似乎这不是一个好主意,这里也发表了评论.然而,它仍然是有趣的和实验的学习经验.
虽然我同意这里的大多数答案,但我认为增加一个新的考虑是值得的:Speedstep技术.
在多核系统上运行CPU密集型单线程作业时,在我的情况下,在Windows Server 2012下运行具有6个真实核心(12个带有HT)的Xeon E5-2430,该作业在所有12个核心中分散,使用每个核心约8.33%,永远不会引发速度提升.CPU保持在1.2 GHz.
当我将线程亲和性设置为特定核心时,它使用了该核心的~100%,导致CPU在2.5 GHz时最大化,使性能提高一倍以上.
这是我使用的程序,它只是循环增加一个变量.当使用-a调用时,它将设置与核心1的亲和性.亲和力部分基于此帖子.
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().Single(x => x.Id == id); } } } }
结果如下:
处理器速度,如任务管理器所示,类似于CPU-Z报告的内容:
您绝对可以通过在程序内部编写例程来完成此操作。
但是,您不应该尝试这样做,因为操作系统是管理这些内容的最佳人选。我的意思是用户模式程序不应尝试这样做。
但是,有时可以做到(对于真正的高级用户),以实现负载平衡,甚至找出真正的多线程多核问题(数据竞速/缓存一致性...),因为不同的线程将真正在不同的处理器上执行。
话虽如此,如果您仍然想实现目标,我们可以通过以下方式实现。我正在为您提供(Windows OS)的伪代码,但是它们也可以在Linux上轻松完成。
#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以了解有关这些概念的更多信息。