假设我有一个使用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的数量:
1 2 3 4
| 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对象作为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。亲和力部分基于该文章。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| 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报告的速度:
您绝对可以通过在程序内部编写例程来完成此操作。
但是,您不应该尝试这样做,因为操作系统是管理这些内容的最佳人选。我的意思是用户模式程序不应尝试这样做。
但是,有时可以做到(对于真正的高级用户),以实现负载平衡,甚至找出真正的多线程多核问题(数据竞速/缓存一致性...),因为不同的线程将真正在不同的处理器上执行。
话虽如此,如果您仍然想要实现,我们可以通过以下方式实现。我正在为您提供(Windows OS)的伪代码,但是它们也可以在Linux上轻松完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #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". |
在调用上述例程之后,线程将始终以以下方式执行:
1 2 3 4 5 6 7 8 9 10 11 12
| Thread1-> Core1
Thread2-> Core2
Thread3-> Core3
Thread4-> Core4
Thread5-> Core5
Thread6-> Core6
Thread7-> Core7
Thread8-> Core8
Thread9-> Core1
Thread10-> Core2
............... |
有关更多信息,请参阅手册/ MSDN以了解有关这些概念的更多信息。
您不必担心自己这样做。我有在双四核计算机上运行的多线程.NET应用程序,无论线程是如何启动的(无论是通过ThreadPool还是手动启动),我都能在所有内核上看到均匀的工作分配。
您无法执行此操作,因为只有操作系统才能执行此操作。如果您决定的话.....那么将很难编写应用程序代码。因为那样,您还需要注意处理器间的通信。关键部分。对于每个应用程序,您都必须创建自己的信号灯或互斥锁……操作系统自己可以对它们提供通用的解决方案。
在不同内核之间拆分线程是操作系统的工作,当线程使用大量CPU时间时,它将自动执行。不用担心至于要找出您的用户有多少个内核,请在C#中尝试Environment.ProcessorCount。
每个线程通常由操作系统本身处理...因此在一个4核系统上生成4个线程,操作系统将决定要在哪个内核上运行,每个内核通常是1个线程。
您不应该(如上所述)尝试自己分配这类内容的原因之一是,您只是没有足够的信息来正确地进行处理,尤其是在NUMA等方面。
如果您有一个要运行的线程,并且有一个内核处于空闲状态,则内核将运行您的线程,请不要担心。