Как порождать потоки на разных ядрах процессора? - PullRequest
55 голосов
/ 28 августа 2008

Допустим, у меня была программа на C #, которая выполняла что-то вычислительно дорогое, например, кодировало список файлов WAV в MP3. Обычно я кодировал файлы по одному, но, скажем, я хотел, чтобы программа выяснила, сколько у меня ядер ЦП, и раскрутила поток кодирования на каждом ядре. Итак, когда я запускаю программу на четырехъядерном процессоре, программа выясняет, что это четырехъядерный процессор, обнаруживает, что есть четыре ядра для работы, а затем создает четыре потока для кодирования, каждый из которых работает на своем ЦПУ. Как бы я это сделал?

И будет ли это иначе, если ядра распределены по нескольким физическим процессорам? Например, если у меня была машина с двумя четырехъядерными процессорами, есть ли какие-то особые соображения или восемь ядер в двух матрицах считаются равными в Windows?

Ответы [ 10 ]

57 голосов
/ 28 августа 2008

Не беспокойтесь об этом.

Вместо этого используйте Thread Pool . Пул потоков - это механизм (на самом деле класс) фреймворка, который вы можете запросить для нового потока.

Когда вы просите новый поток, он либо даст вам новый, либо поставит работу в очередь, пока поток не освободится. Таким образом, фреймворк отвечает за выбор: нужно ли создавать больше потоков или нет, в зависимости от количества имеющихся процессоров.

Редактировать: Кроме того, как уже упоминалось, ОС отвечает за распределение потоков между различными процессорами.

16 голосов
/ 20 февраля 2009

Это не обязательно так просто, как использование пула потоков.

По умолчанию пул потоков выделяет несколько потоков для каждого ЦП. Поскольку каждый поток, участвующий в выполняемой вами работе, имеет свою стоимость (накладные расходы на переключение задач, использование очень ограниченного кэш-памяти L1, L2 и, возможно, L3 и т. Д.), Оптимальное количество используемых потоков - <= количество доступных процессоров - если каждый поток не запрашивает сервисы от других машин - например, веб-сервис с высокой степенью масштабируемости. В некоторых случаях, особенно в тех, которые требуют больше чтения и записи на жестком диске, чем нагрузки на процессор, на самом деле лучше иметь 1 поток, чем несколько потоков. </p>

Для большинства приложений, и, конечно, для кодирования WAV и MP3, вы должны ограничить количество рабочих потоков количеством доступных процессоров. Вот код C # для определения количества процессоров:

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

К сожалению, это не так просто, как ограничить себя количеством процессоров. Вы также должны принять во внимание производительность контроллера (ов) жесткого диска и дисков.

Единственный способ найти оптимальное количество потоков - это пробная ошибка. Это особенно верно, когда вы используете жесткие диски, веб-сервисы и тому подобное. С жесткими дисками вам лучше не использовать все четыре процессора на вашем четырехъядерном процессоре. С другой стороны, с некоторыми веб-сервисами вам может быть лучше сделать 10 или даже 100 запросов на процессор.

8 голосов
/ 28 августа 2008

В случае управляемых потоков сложность выполнения этого на порядок выше, чем у собственных потоков. Это связано с тем, что потоки CLR напрямую не связаны с собственным потоком ОС. Другими словами, CLR может переключать поток управляемый с собственного потока на собственный поток по своему усмотрению. Функция Thread.BeginThreadAffinity предназначена для помещения управляемого потока в шаг блокировки с собственным потоком ОС. В этот момент вы можете поэкспериментировать с использованием нативных API-интерфейсов, чтобы придать базовый родственный процессору потоков родство. Как все предлагают здесь, это не очень хорошая идея. Фактически существует документация , в которой говорится, что потоки могут получать меньше времени обработки, если они ограничены одним процессором или ядром.

Вы также можете изучить класс System.Diagnostics.Process . Там вы можете найти функцию для перечисления потоков процесса в виде коллекции ProcessThread объектов. Этот класс имеет методы для установки ProcessorAffinity или даже установки предпочтительного процессора - не уверен, что это такое.

Отказ от ответственности: у меня возникла похожая проблема, когда я подумал, что ЦП (ы) были использованы недостаточно, и исследовал многие из этих вещей; однако, исходя из всего, что я прочитал, оказалось, что это не очень хорошая идея, о чем свидетельствуют комментарии, размещенные здесь. Тем не менее, это все еще интересно и опыт обучения, чтобы экспериментировать.

6 голосов
/ 01 апреля 2015

Хотя я согласен с большинством ответов здесь, я думаю, что стоит добавить новое соображение: технология Speedstep.

При выполнении ресурсоемкого однопоточного задания в многоядерной системе, в моем случае Xeon E5-2430 с 6 реальными ядрами (12 с HT) под Windows Server 2012, задание распространилось среди всех 12 ядер, используя около 8,33% каждого ядра и никогда не вызывая увеличения скорости. Процессор остался на 1,2 ГГц.

Когда я установил привязку потока к конкретному ядру, оно использовало ~ 100% этого ядра, в результате чего максимальная загрузка процессора составила 2,5 ГГц, что более чем удвоило производительность.

Это программа, которую я использовал, которая просто циклически увеличивает переменную. Когда вызвано с -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);
            }
        }
    }
}

И результаты:

results

Скорость процессора, как показывает диспетчер задач, аналогична той, что сообщает CPU-Z:

enter image description here

3 голосов
/ 29 августа 2013

Вы определенно можете сделать это, написав подпрограмму внутри вашей программы.

Однако вам не следует пытаться это делать, поскольку операционная система - лучший кандидат для управления этими вещами. Я имею в виду, что программа в режиме пользователя не должна пытаться делать это.

Однако, иногда, это может быть сделано (для действительно продвинутого пользователя), чтобы достигнуть балансировки нагрузки и даже обнаружить истинную многопоточную многоядерную проблему (скачки данных / согласованность кэша ...), поскольку различные потоки будут действительно выполняться на другом процессоре.

Сказав это, если вы все еще хотите достичь, мы можем сделать это следующим образом. Я предоставляю вам псевдокод для (ОС 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, чтобы узнать больше об этих понятиях.

3 голосов
/ 28 августа 2008

Вам не нужно беспокоиться о том, чтобы сделать это самостоятельно. У меня есть многопоточные приложения .NET, работающие на машинах с двумя процессорами, и независимо от того, как запускаются потоки, будь то через ThreadPool или вручную, я вижу хорошее равномерное распределение работы по всем ядрам.

2 голосов
/ 18 июня 2012

вы не можете сделать это, так как только операционная система имеет права делать это. Если вы решите это ..... тогда будет сложно кодировать приложения. Потому что тогда вам также нужно позаботиться о межпроцессорной связи. критические разделы. для каждого приложения вы должны создать свои собственные семафоры или мьютексы ...... для которых операционная система дает общее решение, выполняя его самостоятельно .......

2 голосов
/ 28 августа 2008

Задача операционной системы - разделять потоки между разными ядрами, и это будет происходить автоматически, когда ваши потоки используют много процессорного времени. Не беспокойся об этом. Что касается того, чтобы узнать, сколько ядер у вашего пользователя, попробуйте Environment.ProcessorCount в C #.

2 голосов
/ 28 августа 2008

Куда направляется каждый поток, обычно обрабатывается самой ОС ... так что сгенерируйте 4 потока в четырехъядерной системе, и ОС решит, на каких ядрах работать, в каждом из которых обычно по 1 потоку.

1 голос
/ 28 августа 2008

Одна из причин, по которой вы не должны (как уже было сказано) пытаться распределить подобные вещи самостоятельно, заключается в том, что у вас просто недостаточно информации, чтобы сделать это правильно, особенно в будущем с NUMA и т. Д. 1001 *

Если у вас есть поток, доступный для чтения, и ядро ​​не используется, ядро ​​ будет запускать ваш поток, не беспокойтесь.

...