如何在不同的CPU内核上生成线程?

时间:2008-08-28 14:11:21

标签: c# .net windows multithreading

假设我在C#中创建了一个计算成本高昂的程序,比如将WAV文件列表编码为MP3。通常我会一次编码一个文件,但是假设我想让程序计算出我拥有多少CPU核心并在每个核心上启动编码线程。因此,当我在四核CPU上运行该程序时,该程序发现它是一个四核CPU,发现有四个内核可以使用,然后产生四个线程用于编码,每个线程都在自己独立运行中央处理器。我该怎么做?

如果核心分布在多个物理CPU上,这会有什么不同吗?如果我有一台带有两个四核CPU的机器,是否有任何特殊注意事项,或者两个芯片中的八个内核在Windows中是否相同?

10 个答案:

答案 0 :(得分:57)

不要打扰那样做。

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

当你要求一个新线程时,它会给你一个新的线程,或者将一个线程排队,直到一个线程被释放。通过这种方式,框架负责决定是否应创建更多线程,具体取决于当前CPU的数量。

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

答案 1 :(得分:16)

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

默认情况下,线程池为每个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个请求。

答案 2 :(得分:8)

对于托管线程,执行此操作的复杂程度大于本机线程的复杂程度。这是因为CLR线程不直接绑定到本机OS线程。换句话说,CLR可以在其认为合适时将托管线程从本机线程切换到本机线程。提供函数Thread.BeginThreadAffinity是为了将托管线程与本机OS线程锁定在一起。此时,您可以尝试使用本机API来提供底层本机线程处理器关联。正如大家在这里所说,这不是一个好主意。实际上有documentation表明如果线程仅限于单个处理器或核心,则线程可以获得更少的处理时间。

您还可以浏览System.Diagnostics.Process课程。在那里,您可以找到一个函数来枚举进程'线程作为ProcessThread对象的集合。这个类有设置ProcessorAffinity甚至设置首选处理器的方法 - 不知道那是什么。

免责声明:我遇到过类似的问题,我认为CPU的使用率很低,并研究了很多这样的东西;然而,根据我读到的所有内容,似乎这不是一个好主意,这里也发布了评论。然而,它仍然很有趣,也是一种实验的学习经历。

答案 3 :(得分:6)

虽然我同意这里的大多数答案,但我认为增加新的考虑是值得的: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.关联部分基于this post

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);
            }
        }
    }
}

结果:

results

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

enter image description here

答案 4 :(得分:3)

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

答案 5 :(得分:3)

您可以通过在程序中编写例程来实现此目的。

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

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

话虽如此,如果您仍想实现,我们可以通过以下方式实现。我正在为您提供(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以了解有关这些概念的更多信息。

答案 6 :(得分:2)

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

答案 7 :(得分:2)

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

答案 8 :(得分:2)

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

答案 9 :(得分:1)

你不应该(正如已经说过)试图自己分配这类东西的原因之一就是你没有足够的信息来正确地做到这一点,特别是在未来使用NUMA等等。 / p>

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

相关问题