Пул потоков для выполнения произвольных задач с разными приоритетами - PullRequest
9 голосов
/ 02 сентября 2008

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

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

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

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

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

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

Ответы [ 5 ]

5 голосов
/ 02 сентября 2008

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

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

1 голос
/ 02 сентября 2008

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

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

0 голосов
/ 02 сентября 2008

@ DrPizza:

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

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

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

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

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

0 голосов
/ 02 сентября 2008

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

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

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

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

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

0 голосов
/ 02 сентября 2008

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

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

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

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

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