65

假设我有一个 C# 程序,它做了一些计算成本很高的事情,比如将 WAV 文件列表编码为 MP3。通常我会一次对一个文件进行编码,但假设我想让程序弄清楚我有多少个 CPU 内核并在每个内核上启动一个编码线程。所以,当我在四核 CPU 上运行程序时,程序会发现它是四核 CPU,发现有四个内核可以使用,然后生成四个线程进行编码,每个线程都单独运行中央处理器。我该怎么做?

如果内核分布在多个物理 CPU 上,情况会有什么不同吗?例如,如果我的机器上有两个四核 CPU,是否有任何特殊考虑,或者在 Windows 中两个芯片上的八个内核是否相等?

4

10 回答 10

62

不要打扰这样做。

而是使用线程池。线程池是框架的一种机制(实际上是一个类),您可以查询新线程。

当你请求一个新线程时,它要么给你一个新线程,要么将工作排入队列,直到一个线程被释放。这样,框架负责根据当前 CPU 的数量决定是否应该创建更多线程。

编辑:此外,正如已经提到的,操作系统负责在不同的 CPU 之间分配线程。

于 2008-08-28T14:13:41.920 回答
18

不一定像使用线程池那么简单。

默认情况下,线程池为每个 CPU 分配多个线程。由于参与您正在做的工作的每个线程都有成本(任务切换开销,使用 CPU 非常有限的 L1、L2 和 L3 缓存等...),使用的最佳线程数是 <=可用 CPU 的数量 - 除非每个线程都从其他机器请求服务 - 例如高度可扩展的 Web 服务。在某些情况下,尤其是那些涉及更多硬盘读写而不是 CPU 活动的情况,实际上使用 1 个线程比使用多个线程更好。

对于大多数应用程序,当然对于 WAV 和 MP3 编码,您应该将工作线程的数量限制为可用 CPU 的数量。这是一些用于查找 CPU 数量的 C# 代码:

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 个请求可能会更好。

于 2009-02-20T01:54:23.680 回答
13

尽管我同意这里的大部分答案,但我认为值得添加一个新的考虑因素: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<ProcessThread>().Single(x => x.Id == id);
            }
        }
    }
}

结果:

结果

处理器速度,如任务管理器所示,类似于 CPU-Z 报告的内容:

在此处输入图像描述

于 2015-04-01T13:25:18.610 回答
9

在托管线程的情况下,这样做的复杂性比本地线程要高。这是因为 CLR 线程不直接绑定到本机 OS 线程。换句话说,CLR 可以在它认为合适的时候将托管线程从本机线程切换到本机线程。提供函数Thread.BeginThreadAffinity以将托管线程与本机 OS 线程同步。那时,您可以尝试使用本机 API 来提供底层本机线程处理器的亲和性。正如每个人在这里所建议的那样,这不是一个好主意。事实上,有文档表明,如果线程仅限于单个处理器或内核,则它们可以接收更少的处理时间。

您还可以探索System.Diagnostics.Process类。在那里,您可以找到一个函数来将进程的线程枚举为ProcessThread对象的集合。此类具有设置 ProcessorAffinity 甚至设置首选处理器的方法——不确定那是什么。

免责声明:我遇到过类似的问题,我认为 CPU 未得到充分利用并研究了很多这样的东西;但是,根据我阅读的所有内容,这似乎不是一个好主意,此处发布的评论也证明了这一点。但是,它仍然很有趣,并且可以进行实验。

于 2008-08-28T14:49:52.443 回答
6

您绝对可以通过在程序中编写例程来做到这一点。

但是你不应该尝试这样做,因为操作系统是管理这些东西的最佳选择。我的意思是用户模式程序不应该尝试这样做。

但是,有时,可以(对于真正的高级用户)实现负载平衡,甚至找出真正的多线程多核问题(数据竞争/缓存一致性......),因为不同的线程将真正在不同的处理器上执行.

话虽如此,如果您仍然想实现我们可以通过以下方式实现。我为您提供了(Windows 操作系统)的伪代码,但它们也可以在 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 以了解有关这些概念的更多信息。

于 2013-08-28T23:28:49.530 回答
3

您不必担心自己会这样做。我有在双四核机器上运行的多线程 .NET 应用程序,无论线程如何启动,无论是通过 ThreadPool 还是手动启动,我都看到所有内核的工作分布非常均匀。

于 2008-08-28T14:16:11.337 回答
2

每个线程的去向通常由操作系统本身处理......因此在 4 核系统上生成 4 个线程,操作系统将决定在哪些内核上运行每个内核,这通常是每个内核上的 1 个线程。

于 2008-08-28T14:13:01.877 回答
2

将线程拆分到不同的内核是操作系统的工作,当您的线程使用大量 CPU 时间时,它会自动执行此操作。别担心。至于找出您的用户有多少个内核,请尝试Environment.ProcessorCount使用 C#。

于 2008-08-28T14:13:57.367 回答
2

你不能这样做,因为只有操作系统才有权限这样做。如果您决定这样做......那么编写应用程序将很困难。因为那时您还需要注意处理器间的通信。关键部分。对于每个应用程序,您必须创建自己的信号量或互斥体......操作系统通过自己提供通用解决方案......

于 2012-06-18T15:19:25.433 回答
1

您不应该(如前所述)尝试自己分配此类东西的原因之一是您没有足够的信息来正确执行此操作,尤其是在未来使用 NUMA 等时。

如果你有一个线程读取运行,并且有一个核心空闲,内核运行你的线程,不用担心。

于 2008-08-28T14:29:39.730 回答