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

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

пул потоков должен выполнить ряд задач. Задачи могут быть кратковременными (

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

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

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

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

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

  • a - 1000 экземпляров - низкий приоритет-max tasks 1
  • B - 1000 экземпляров - низкий приоритет-max tasks 1
  • C - 1000 экземпляров - низкий приоритет-max задачи 1

A рабочая реализация может выполняться только (не более) 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();
    };
}

5 ответов


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

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


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

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


@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 (и maximal параллелизм 1), но на этот раз с высокий приоритет, что он должен делать?

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

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

  • задача - 1000 экземпляры-низкий приоритет
  • B задача-1000 экземпляров-высокий приоритет

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