Одним из флагом функции QueueUserWorkItem является WT_EXECUTELONGFUNCTION. Документация для этого флага говорит:
Функция обратного вызова может выполняться длительное время. Этот флаг помогает системе решить, надо ли ей создавать новый поток.
Как указано в документации, пул потоков использует этот флаг для решения, надо ли ему создавать новый поток или же ждать завершения работы запущенного элемента. Если все потоки текущего пула потоков заняты работой и появляется новая задача, то она будет ждать завершения любой уже запущенной задачи, если они "короткие", потому что ожидается, что они очень скоро завершатся и освободят поток для запуска следующей задачи. С другой стороны, если все задачи помечены флагом WT_EXECUTELONGFUNCTION, то пул потоков знает, что ожидание завершения таких задач не будет очень-то продуктивным, поэтому он предпочтёт создать новый поток для запуска новой задачи.
Если вы не укажете флаг WT_EXECUTELONGFUNCTION для "долгой" задачи, то пул потоков будет ожидать завершения этой задачи до запуска новой, когда ему следовало бы создать новый поток немедленно. Если ваша задача не завершиться быстро, то в итоге у пула потоков закончится терпение, и он поймёт, что вы ему наврали - так что он всё равно создаст новый поток. Но это займёт некоторое время - пока пул потоков осознает, что он ждал напрасно.
Давайте продемонстрируем это простой консольной программой (прим.пер.: этот пример будет демонстрировать задуманное только на однопроцессорной машине):
program Project1; {$APPTYPE CONSOLE} uses Windows, SysUtils; var LastTick: Cardinal; procedure Tick(Context: Pointer; Success: Boolean); stdcall; var Tick: Cardinal; begin Tick := GetTickCount; Writeln(Format('%5d', [Tick - LastTick])); end; function Clog(Context: Pointer): Integer; stdcall; begin Sleep(4000); Result := 0; end; var hTimer: THandle; begin LastTick := GetTickCount; case ParamCount of 1: QueueUserWorkItem(Clog, nil, 0); 2: QueueUserWorkItem(Clog, nil, WT_EXECUTELONGFUNCTION); end; CreateTimerQueueTimer(hTimer, 0, Tick, nil, 250, 250, 0); Sleep(INFINITE); end.Эта программа создаёт задачу каждый раз, когда срабатывает таймер Tick - примерно каждые 250 мс. Сама задача просто печатает, как много времени прошло с момента запуска. Для начала просто запустите программу, без параметров - заметьте, что задача срабатывает примерно каждые 250 мс, как и ожидалось:
251 501 751 1012 ^CЗатем запустите программу с одним (любым) параметром командной строки. Это приведёт к срабатыванию первого случая для case, где добавляется задача "Clog". "Clog" делает, что и означает её имя ("пробка", "затор"): она тормозит очередь задач задерживая своё выполнение на длительное время (четыре секунды). Заметьте, что теперь первый вызов не происходит целую секунду:
1001 1011 1021 1021 1252 1502 1752 ^CЭто потому что мы поставили в очередь задачу "Clog" без указания флага WT_EXECUTELONGFUNCTION. Другими словами, мы сказали пулу потоков: "А, не волнуйся за этого парня, сейчас он всё быстро сделает". Затем пул потоков хочет запустить задачу Tick, а поскольку задача Clog была помечена как "быстрая", то он решает подождать её, чтобы повторно использовать поток, вместо создания нового. После примерно секунды у пула потоков кончается терпение, и он создаёт новый поток, чтобы обслужить скопившиеся в очереди задачи Tick.
Заметьте, что как только срабатывает первая задача Tick, также срабатывают ещё три задачи - друг за другом. Это потому что пул потоков осознал, что он не выполнил четыре задачи (спасибо затору, созданному Clog), и поэтому он выполняет их немедленно - только для того, чтобы очистить очередь задач. Пятый и дальнейшие вызовы срабатывают примерно вовремя, потому что пул потоков уже знает, что задача Clog является тормозом, и пометил её как "длительную".
Наконец, запустите программу с двумя (любыми) аргументами командной строки. Это приведёт к срабатыванию второй строки в case, где мы планируем всю ту же задачу Clog, но теперь уже указывая флаг WT_EXECUTELONGFUNCTION:
251 511 761 1012 ^CЗаметьте, что с этой подсказкой, пул потоков больше не будет одурачен задачей Clog - и он значет, что ему надо создать новый поток для обработки задач Tick.
Мораль истории: если вы собираетесь работать с пулом потоков - убедитесь, что вы дружелюбны к другим задачам и уведомляете пул потоков о времени, которое вам понадобится для работы. Это позволит пулу потоков создавать минимум рабочих потоков (и, таким образом, использовать преимущества пула), но при этом обеспечивать максимально гладкое выполнение входящих задач.
Упражнение: каковы последствия для пула потоков, если вы создадите таймер пула потока, чья функция обратного вызова выполняется дольше, чем интервал этого таймера?
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.