69 Stimmen

Wie kann ich Threads auf verschiedenen CPU-Kernen erzeugen?

Nehmen wir an, ich hätte ein Programm in C#, das etwas rechenintensives macht, wie das Kodieren einer Liste von WAV-Dateien in MP3s. Normalerweise würde ich die Dateien einzeln kodieren, aber ich wollte, dass das Programm herausfindet, wie viele CPU-Kerne ich habe und auf jedem Kern einen Kodierungsthread startet. Wenn ich das Programm also auf einer Quad-Core-CPU ausführe, stellt das Programm fest, dass es sich um eine Quad-Core-CPU handelt, findet heraus, dass es vier Kerne gibt, mit denen es arbeiten kann, und legt dann vier Threads für die Codierung an, von denen jeder auf einer eigenen CPU läuft. Wie würde ich das machen?

Und wäre das anders, wenn die Kerne auf mehrere physische CPUs verteilt wären? Wenn ich also eine Maschine mit zwei Quad-Core-CPUs hätte, gibt es dann irgendwelche besonderen Überlegungen oder werden die acht Kerne auf den beiden Dies in Windows als gleichwertig betrachtet?

65voto

Jorge Córdoba Punkte 49057

Machen Sie sich nicht die Mühe, das zu tun.

Verwenden Sie stattdessen die Gewinde-Pool . Der Thread-Pool ist ein Mechanismus (eigentlich eine Klasse) des Frameworks, den Sie nach einem neuen Thread abfragen können.

Wenn Sie um einen neuen Thread bitten, wird er Ihnen entweder einen neuen geben oder die Arbeit in die Warteschlange stellen, bis ein Thread frei wird. Auf diese Weise entscheidet das Framework selbst, ob es je nach Anzahl der vorhandenen CPUs weitere Threads erstellen soll oder nicht.

Edit: Darüber hinaus ist, wie bereits erwähnt, das Betriebssystem für die Verteilung der Threads auf die verschiedenen CPUs zuständig.

18voto

Joe Erickson Punkte 6899

Es ist nicht unbedingt so einfach wie die Verwendung des Threadpools.

Standardmäßig weist der Thread-Pool mehrere Threads für jede CPU zu. Da jeder Thread, der an Ihrer Arbeit beteiligt ist, Kosten verursacht (Overhead durch Taskwechsel, Nutzung des sehr begrenzten L1-, L2- und vielleicht L3-Cache der CPU usw.), ist die optimale Anzahl der zu verwendenden Threads <= der Anzahl der verfügbaren CPUs - es sei denn, jeder Thread fordert Dienste von anderen Rechnern an - wie z. B. ein hoch skalierbarer Webdienst. In einigen Fällen, vor allem bei solchen, die mehr Lese- und Schreibvorgänge auf der Festplatte als CPU-Aktivitäten beinhalten, kann es sogar besser sein, einen Thread zu verwenden als mehrere Threads.

Für die meisten Anwendungen, insbesondere für die WAV- und MP3-Kodierung, sollten Sie die Anzahl der Worker-Threads auf die Anzahl der verfügbaren CPUs beschränken. Hier ist ein C#-Code, um die Anzahl der CPUs zu ermitteln:

int processors = 1;
string processorsStr = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
if (processorsStr != null)
    processors = int.Parse(processorsStr);

Leider ist es nicht so einfach, sich auf eine bestimmte Anzahl von CPUs zu beschränken. Sie müssen auch die Leistung des/der Festplatten-Controller(s) und der Festplatte(n) in Betracht ziehen.

Die einzige Möglichkeit, die optimale Anzahl von Fäden zu finden, ist Versuch und Irrtum. Dies gilt insbesondere bei der Verwendung von Festplatten, Webdiensten und dergleichen. Bei Festplatten ist es vielleicht besser, wenn Sie nicht alle vier Prozessoren Ihrer Quad-Prozessor-CPU verwenden. Andererseits ist es bei einigen Webdiensten vielleicht besser, wenn Sie 10 oder sogar 100 Anfragen pro CPU durchführen.

14voto

AlexDev Punkte 3355

Obwohl ich mit den meisten Antworten hier übereinstimme, denke ich, dass es sich lohnt, eine neue Überlegung hinzuzufügen: Die Speedstep-Technologie.

Bei der Ausführung eines CPU-intensiven Single-Thread-Auftrags auf einem Multi-Core-System, in meinem Fall einem Xeon E5-2430 mit 6 echten Kernen (12 mit HT) unter Windows Server 2012, wurde der Auftrag auf alle 12 Kerne verteilt, wobei etwa 8,33 % jedes Kerns verwendet wurden und nie eine Geschwindigkeitserhöhung ausgelöst wurde. Die CPU blieb bei 1,2 GHz.

Wenn ich die Thread-Affinität auf einen bestimmten Kern einstellte, wurde dieser Kern zu 100 % ausgelastet, so dass die CPU mit maximal 2,5 GHz arbeitete und sich die Leistung mehr als verdoppelte.

Dies ist das Programm, das ich verwendet habe und das einfach eine Variable in einer Schleife erhöht. Wenn es mit -a aufgerufen wird, setzt es die Affinität auf Kern 1. Der Affinitätsteil basierte auf diese Stelle .

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

Und die Ergebnisse:

results

Prozessorgeschwindigkeit, wie vom Task-Manager angezeigt, ähnlich wie von CPU-Z gemeldet:

enter image description here

9voto

Peter Meyer Punkte 25181

Im Fall von verwalteten Threads ist die Komplexität dieses Vorgangs um einiges höher als bei nativen Threads. Das liegt daran, dass CLR-Threads nicht direkt mit einem nativen OS-Thread verbunden sind. Mit anderen Worten, die CLR kann einen verwaltet Thread von nativem Thread zu nativem Thread nach eigenem Ermessen. Die Funktion Thread.BeginThreadAffinity wird bereitgestellt, um einen verwalteten Thread im Gleichschritt mit einem nativen OS-Thread zu platzieren. An diesem Punkt könnten Sie mit der Verwendung nativer APIs experimentieren, um dem zugrunde liegenden nativen Thread Prozessoraffinität zu verleihen. Wie hier bereits angedeutet wurde, ist dies keine besonders gute Idee. In der Tat gibt es Dokumentation was darauf hindeutet, dass Threads weniger Verarbeitungszeit erhalten können, wenn sie auf einen einzigen Prozessor oder Kern beschränkt sind.

Sie können auch die System.Diagnostik.Prozess Klasse. Dort finden Sie eine Funktion zur Aufzählung der Threads eines Prozesses als eine Sammlung von ProzessThread Objekte. Diese Klasse hat Methoden, um ProcessorAffinity zu setzen oder sogar eine bevorzugt Prozessor - nicht sicher, was das ist.

Haftungsausschluss: Ich habe ein ähnliches Problem erlebt, bei dem ich dachte, dass die CPU(s) zu wenig ausgelastet waren und habe viel über dieses Thema recherchiert; nach allem, was ich gelesen habe, schien es jedoch, dass es keine sehr gute Idee war, wie die hier geposteten Kommentare zeigen. Aber es ist immer noch interessant und eine Lernerfahrung zu experimentieren.

6voto

Mantosh Kumar Punkte 5455

Sie können dies auf jeden Fall tun, indem Sie die Routine in Ihr Programm schreiben.

Sie sollten jedoch nicht versuchen, dies selbst zu tun, da das Betriebssystem am besten geeignet ist, diese Dinge zu verwalten. Ich meine, ein Programm im Benutzermodus sollte das nicht tun.

Manchmal ist es jedoch möglich (für wirklich fortgeschrittene Benutzer), eine Lastverteilung zu erreichen und sogar ein echtes Multi-Thread-Multi-Core-Problem (Datenrennen/Cache-Kohärenz...) herauszufinden, da verschiedene Threads wirklich auf verschiedenen Prozessoren ausgeführt werden würden.

Wenn Sie trotzdem etwas erreichen wollen, können wir es auf folgende Weise tun. Ich stelle Ihnen den Pseudocode für (Windows OS) zur Verfügung, er könnte jedoch auch leicht unter Linux ausgeführt werden.

#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".

Nach dem Aufruf der obigen Routine würden die Threads immer in der folgenden Weise ausgeführt werden:

Thread1-> Core1
Thread2-> Core2
Thread3-> Core3
Thread4-> Core4
Thread5-> Core5
Thread6-> Core6
Thread7-> Core7
Thread8-> Core8

Thread9-> Core1
Thread10-> Core2
...............

Weitere Informationen finden Sie im Handbuch/MSDN, um mehr über diese Konzepte zu erfahren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X