Библиотека сайта rus-linux.net
Назад | Сервер TCP/IP ... много серверов хороших и разных | Вперед |
Оптимальное управление потоками (пул)
Различные модели серверов отрабатываются в расчёте на обслуживание интенсивного потока (десятки и сотни одновременно) обслуживания со сложным, непредсказуемым характером изменения интенсивности по времени, часто с взрывным характером. Это тот класс задач, который часто называют: сервера массового обслуживания. Если сервер должен обслуживать достаточно равномерный по интенсивности поток, пусть это даже будет и весьма высокая интенсивность, то здесь вполне достаточным решением был бы классический параллельный сервер (на fork(), или на pthread_create()). Но если рассчитывать на взрывообразные всплески интенсивности запросов, то непосредственное создание обрабатывающего потока (процесса) либо вслед за accept(), либо перед ним — не будет оптимальным. Оптимальным было бы создание заблаговременно некоторого «запаса» обрабатывающих ветвей (процессов, потоков), которые готовы были бы немедленно включаться в обработку при поступлении запроса. Такие конструкции хорошо известны и называются «пул», причём имеют место быть несколько их видов:
- Фиксированный пул — когда можно прогнозировать максимальное число единовременно поступающих запросов, и под это число создаётся равное фиксированное число ветвей обработки. При поступлении запросов обрабатывающие ветви буду черпаться из этого пула циклически, а после завершения обработчик будет возвращаться в пул. Пример такого (фиксированного пула процессов) у нас уже встречался (файл ecH21.cc — 3 процесса в пуле), но там мы не стали акцентировать на этом внимания, поскольку речь шла о другом.
- Динамический пул — когда ветви обработчика динамически создаются в пул (и уничтожаются из него) так, чтобы в пуле всегда сохранялось некоторое рациональное число обработчиков, готовых немедленно вступить в обработку. В API операционной системы реального времени QNX 6.X, например, реализована встроенная модель такой конструкции, заимствованная из рекомендаций одного из расширений реального времени стандарта POSIX: типы данных thread_pool_attr_t и THREAD_POOL_PARAM_T, функциональные вызовы thread_pool_create() и thread_pool_start() (для наглядности — как это выглядит — файл работающего примера ecH31.cc оставлен в архиве, но это не работает в Linux). Алгоритмика такого пула достаточно остроумна:
- обработчики создаются и уничтожаются в пул за один раз не поодиночке, а пачками, для определённости по M (например: 3, 5) штук;
- устанавливаются параметры пула: L - «нижняя ватерлиния» (например: 5) и H — верхняя ватерлиния (например: 11);
- первоначально в пул создаётся (по M штук) такое число обработчиков (L%M+1), чтобы оно минимально превосходило L (здесь % - операция целочисленного деления);
- если при массированном поступлении запросов число свободных обработчиков в пуле становится меньше L, то создаётся дополнительно (пачками по M) некоторое их число, так, чтобы в итоге, число свободных превышало L;
- при завершении обработки текущий обработчик не уничтожается, а возвращается в пул ... до тех пор, пока число свободных обработчиков в пуле не превысит H;
- в последнем случае обработчики начинают ликвидироваться (пачками по M), так, чтобы число свободных стало меньше H;
- таким образом постоянно (за вычетом редких переходных процессов) поддерживается число свободных обработчиков в пуле превышающее L, но не превосходящее H.
Модель динамического пула — это одна из самых плодотворных моделей в организации параллельных серверов. Ясно, что реализовать модель хоть фиксированного, хоть динамического пула в коде своего сервера не представляет большого труда и в Linux (здесь важнее ясность понимания того, что мы хотим реализовать).
Назад | Сервер TCP/IP ... много серверов хороших и разных | Вперед |