Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform
Шрифт:
Я указал флаг POOL_FLAG_USE_SELF, что означает, что поток, вызвавший функцию thread_pool_start, будет рассматриваться как доступный для ввода в пул. Таким образом, на момент старта пула в нем есть только один поток. Поскольку значение параметра lo_water равно 3, библиотека немедленно создаст еще increment потоков (в нашем случае — 2). С этого момента в пуле будет три (3) потока, и все они будут находиться в режиме блокирования. Условие по параметру lo_water удовлетворено, потому что число потоков в режиме блокирования действительно не меньше lo_water, условие по параметру hi_water удовлетворено, потому что число потоков в режиме блокирования действительно не больше hi_water; и, наконец, также удовлетворено условие по параметру maximum, потому что общее число потоков не превышает его значения. Допустим теперь, что один из потоков, находящихся в режиме блокирования, разблокируется (например, в серверном приложении — при получении сообщения).
Пусть далее разблокируется еще несколько потоков. Давайте предположим, что на этот момент еще ни один из потоков, находящихся в режиме обработки, еще не завершил свои дела. Ниже приведена таблица, в которой иллюстрируется весь процесс, начиная с исходного состояния:
Событие | Режим обработки | Режим блокирования | Всего потоков |
---|---|---|---|
Исходное состояние | 0 | 1 | 1 |
Срабатывание триггера lo_water | 0 | 3 | 3 |
Разблокирование | 1 | 2 | 3 |
Срабатывание триггера lo_water | 1 | 4 | 5 |
Разблокирование | 2 | 3 | 5 |
Разблокирование | 3 | 2 | 5 |
Срабатывание триггера lo_water | 3 | 4 | 7 |
Разблокирование | 4 | 3 | 7 |
Разблокирование | 5 | 2 | 7 |
Срабатывание триггера lo_water | 5 | 4 | 9 |
Разблокирование | 6 | 3 | 9 |
Разблокирование | 7 | 2 | 9 |
Срабатывание триггера lo_water | 7 | 3 | 10 |
Разблокирование | 8 | 2 | 10 |
Разблокирование | 9 | 1 | 10 |
Разблокирование | 10 | 0 | 10 |
Видно, что библиотека проверяет параметр lo_water, и по мере необходимости увеличивает число потоков на значение параметра increment, но только до тех пор, пока число потоков не достигнет предельного значения — параметра maximum (именно поэтому число в столбце «Всего потоков» никогда не превышает 10, даже когда условие по параметру lo_water перестает выполняться).
Это означает, что однажды наступает момент, когда потоков в режиме блокирования больше не остается. Предположим теперь, что потоки, находящиеся в режиме обработки, завершают свои дела. Посмотрим, что при этом произойдет с триггером параметра hi_water.
Событие | Режим обработки | Режим блокирования | Всего потоков |
---|---|---|---|
Завершение обработки | 9 | 1 | 10 |
Завершение обработки | 8 | 2 | 10 |
Завершение обработки | 7 | 3 | 10 |
Завершение обработки | 6 | 4 | 10 |
Завершение обработки | 5 | 5 | 10 |
Завершение обработки | 4 | 6 | 10 |
Завершение обработки | 3 | 7 | 10 |
Завершение обработки | 2 | 8 | 10 |
Срабатывание триггера hi_water | 2 | 7 | 9 |
Завершение обработки | 1 | 8 | 9 |
Срабатывание триггера hi_water | 1 | 7 | 9 |
Завершение обработки | 0 | 8 | 8 |
Срабатывание триггера hi_water | 0 | 7 | 7 |
Обратите внимание, что с потоками ничего не происходит до тех пор, пока число блокированных потоков не превышает значение hi_water. Реализация здесь такова: как только поток завершает обработку, он проверяет число блокированных на данный момент потоков, и если их слишком много (то есть больше, чем предусмотрено параметром hi_water), то «совершает самоубийство». Удобство использования параметров lo_water и hi_water в управляющих структурах состоит в том, что ими вы фактически задаете «эффективный диапазон» числа потоков, в пределах которого всегда доступно достаточное число потоков, и потоки без необходимости не создаются и не уничтожаются. В нашем случае, после выполнения действий, перечисленных в вышеупомянутых таблицах, мы имеем систему, которая способна обрабатывать до 4 запросов одновременно без необходимости в создании дополнительных потоков (7-4 = 3, что соответствует значению параметра lo_ water).
Теперь, когда мы достаточно хорошо владеем методикой управления числом потоков в пуле, давайте обратимся к другим элементам атрибутной записи пула потоков:
Повторно обратимся к рисунку «Жизненный цикл пула потоков». Из рисунка видно, что при создании потока каждый раз вызывается функция context_alloc. (Аналогично, при уничтожении потока вызывается функция context_tree). Элемент атрибутной записи с именем handler передается функции context_alloc в качестве ее единственного параметра. Функция context_alloc ответственна за индивидуальные настройки потока и возвращает указатель на контекст (списках параметров называемый ctp). Заметьте, что содержание этого указателя — исключительно ваша забота; библиотеке абсолютно все равно, что вы в него поместите.
Теперь, когда контекст создан функцией context_alloc, вызывается функция block_func для перевода потока в режим блокирования. Заметьте, что функция block_func получает на вход результат работы функции context_alloc. После того как функция block_func разблокируется, она возвращает указатель на контекст, который библиотека передает функции handler_func. Функция handler_func отвечает за выполнение «работы» — например, в типовом варианте именно она обрабатывает сообщение от клиента. На данный момент принято, что функция handler_func должна возвращать нуль — ненулевые значения зарезервированы QSSL для будущего функционального расширения. Функция unblock_func также в настоящее время зарезервирована, поэтому просто оставьте там NULL.
Возможно, ситуацию немного прояснит приведенный ниже пример псевдокода (он основан все на том же рисунке «Жизненный цикл потока в пуле потоков»):
Отметим, что приведенная выше программа излишне упрощена. Ее назначение состоит только в том, чтобы продемонстрировать вам поток данных по параметрам ctp и handler и дать вам некоторое представление об алгоритмах, которые обычно применяются для управления числом потоков.