Пул потоков для выполнения произвольных задач с разными приоритетами

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

  •  09-06-2019
  •  | 
  •  

Вопрос

Я пытаюсь придумать дизайн пула потоков с множеством требований к дизайну для моей работы.Это настоящая проблема для работающего программного обеспечения, и это непростая задача.У меня есть рабочая реализация, но я бы хотел передать ее SO и посмотреть, какие интересные идеи могут придумать люди, чтобы я мог сравнить ее со своей реализацией и посмотреть, как она будет работать.Я постарался максимально точно описать требования.

Пулу потоков необходимо выполнить ряд задач.Задачи могут быть кратковременными (<1 секунды) или длительными (часы или дни).Каждая задача имеет соответствующий приоритет (от 1 = очень низкий до 5 = очень высокий).Задачи могут поступать в любое время, пока выполняются другие задачи, поэтому по мере их поступления пул потоков должен их подхватывать и планировать по мере появления потоков.

Приоритет задачи полностью не зависит от длины задачи.На самом деле невозможно сказать, сколько времени может занять выполнение задачи, не просто запустив ее.

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

Основная цель пула потоков — максимизировать пропускную способность.Пул потоков должен эффективно использовать ресурсы компьютера.В идеале для задач, связанных с ЦП, количество активных потоков должно равняться количеству ЦП.Для задач, связанных с вводом-выводом, следует выделять больше потоков, чем имеется процессоров, чтобы блокировка не оказывала чрезмерного влияния на пропускную способность.Важно свести к минимуму использование блокировок и использовать потокобезопасные/быстрые контейнеры.

Как правило, вам следует запускать задачи с более высоким приоритетом и более высоким приоритетом процессора (ссылка:SetThreadPriority).Задачи с более низким приоритетом не должны «блокировать» запуск задач с более высоким приоритетом, поэтому, если задача с более высоким приоритетом появляется во время выполнения всех задач с низким приоритетом, задача с более высоким приоритетом будет запущена.

С задачами связан параметр «максимальное количество выполняемых задач».Каждому типу задачи разрешено одновременно запускать не более указанного количества одновременных экземпляров задачи.Например, у нас могут быть в очереди следующие задачи:

  • A – 1000 экземпляров – низкий приоритет – максимальное количество задач 1
  • B — 1000 экземпляров — низкий приоритет — максимальное количество задач 1
  • C – 1000 экземпляров – низкий приоритет – максимальное количество задач 1

Работающая реализация может одновременно запускать только (максимум) 1 A, 1 B и 1 C.

Он должен работать на Windows XP, Server 2003, Vista и Server 2008 (последние пакеты обновлений).


Для справки мы могли бы использовать следующий интерфейс:

namespace ThreadPool
{
    class Task
    {
    public:
        Task();     
        void run();
    };

    class ThreadPool
    {    
    public:
        ThreadPool();
        ~ThreadPool();

        void run(Task *inst);
        void stop();
    };
}
Это было полезно?

Решение

Итак, что мы собираемся выбрать в качестве основного строительного блока для этого.В Windows есть два многообещающих строительных блока: порты завершения ввода-вывода (IOCP) и асинхронные вызовы процедур (APC).Оба из них дают нам организацию очереди FIFO без необходимости выполнения явной блокировки и с определенной встроенной поддержкой ОС в таких местах, как планировщик (например, IOCP могут избежать некоторых переключений контекста).

БТР, возможно, подходят немного лучше, но с ними придется быть немного осторожными, поскольку они не совсем «прозрачны».Если рабочий элемент выполняет ожидание с предупреждением (::SleepEx,::WaitForXxxObjectEx и т. д.), и мы случайно отправили APC в поток, то вновь отправленный APC возьмет на себя управление потоком, приостанавливая ранее выполнявшийся APC до тех пор, пока не будет создан новый APC. законченный.Это плохо сказывается на наших требованиях к параллелизму и может повысить вероятность переполнения стека.

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

Он должен работать на Windows XP, Server 2003, Vista и Server 2008 (последние пакеты обновлений).

Какая особенность встроенных в систему пулов потоков делает их непригодными для вашей задачи?Если вы хотите использовать XP и 2003, вы не можете использовать новые блестящие пулы Vista/2008, но вы все равно можете использовать QueueUserWorkItem и его друзей.

@DrPizza - это очень хороший вопрос, который затрагивает суть проблемы.Есть несколько причин, по которым QueueUserWorkItem и пул потоков Windows NT были исключены (хотя пул потоков Vista действительно выглядит интересно, возможно, через несколько лет).

Во-первых, мы хотели иметь больший контроль над запуском и остановкой потоков.Мы слышали, что пул потоков NT неохотно запускает новый поток, если считает, что задачи выполняются недолго.Мы могли бы использовать WT_EXECUTELONGFUNCTION, но на самом деле понятия не имеем, длинная задача или короткая.

Во-вторых, если бы пул потоков уже был заполнен длительно выполняющимися задачами с низким приоритетом, не было бы никаких шансов на своевременный запуск задачи с высоким приоритетом.В пуле потоков NT нет реальной концепции приоритетов задач, поэтому мы не можем создать QueueUserWorkItem и сказать: «Да, кстати, запусти это прямо сейчас».

В-третьих, (согласно MSDN) пул потоков NT несовместим с моделью квартиры STA.Я не совсем понимаю, что это значит, но все наши рабочие потоки выполняются в STA.

@DrPizza - это очень хороший вопрос, который затрагивает суть проблемы.Есть несколько причин, по которым QueueUserWorkItem и пул потоков Windows NT были исключены (хотя пул потоков Vista действительно выглядит интересно, возможно, через несколько лет).

Да, похоже, что в Vista его значительно усилили, и теперь он стал довольно универсальным.

Хорошо, мне все еще немного непонятно, как вы хотите, чтобы работали приоритеты.Если в пуле в данный момент выполняется задача типа A с максимальным параллелизмом 1 и низким приоритетом, и ему дается новая задача также типа A (и с максимальным параллелизмом 1), но на этот раз с высоким приоритетом, что ему следует делать? ?

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

Я предполагаю, что вы предпочитаете именно последнее поведение?

@DrPizza:

Хорошо, мне все еще немного непонятно, как вы хотите, чтобы работали приоритеты.Если в пуле в данный момент выполняется задача типа A с максимальным параллелизмом 1 и низким приоритетом, и ему дается новая задача также типа A (и с максимальным параллелизмом 1), но на этот раз с высоким приоритетом, что ему следует делать? ?

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

Обычно только разные типы задач имеют разные приоритеты.Например:

  • Задача – 1000 экземпляров – низкий приоритет
  • Задача Б — 1000 экземпляров — высокий приоритет

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

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