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