Как мне создавать потоки на разных ядрах процессора?

StackOverflow https://stackoverflow.com/questions/32343

Вопрос

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

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

Это было полезно?

Решение

Не утруждайте себя этим.

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

Хотя я согласен с большинством приведенных здесь ответов, я думаю, стоит добавить новое соображение:Технология 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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top