вторник, 11 августа 2015 г.

[prog.thoughts] Сценарии для различных типов приоритетного обслуживания

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


Первый тип обслуживания: одна общая рабочая нить, низкоприоритеные события не обслуживаются пока есть высокоприоритетные.

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

Представьте себе, что вы стоите не остановке. С одной стороны рядом с вами пожилая бабушка, с другой стороны -- пацан лет десяти. Допустим, вокруг ничего не происходит, все нормально. И пацан спрашивает: "Скажите, пожалуйста, который час?" Вы смотрите на часы и отвечаете ему. Все идет своим чередом, ничего экстраординарного.

А теперь предположим, что пожилая бабушка вскрикнула и вы краем глаза замечаете, что она падает. В этот момент пацан вас спрашивает: "Скажите, пожалуйста, которой час?". У вас получается два потока событий, на которые нужно реагировать. Но это потоки с разными приоритетами. Вероятно, вы сначала среагируете на вскрик бабушки и обработаете какой-то связанный с этим поток новых событий: повернетесь к ней, оцените обстановку, позвоните в "Скорую" и т.д. И только после этого, возможно, у вас найдется время ответить на вопрос пацану.

Тип обслуживания событий на одной нити с блокированием низкоприоритетных событий пока есть высокоприоритеные служит как раз для реализации подобных сценариев: т.е. реагирование на поток событий, которые для приложения в данный момент более важны, чем все остальные. Скажем, если вы из программы управляете промышленным оборудованием, то, скорее всего, вы должны обрабатывать сигналы о проблемах в работе оборудования в первую очередь. И лишь когда поток высокоприоритетных событий закончится, сможете перейти к обработке потока остальных событий.


Второй тип обслуживания: одна общая нить, каждый приоритет имеет квоту.

Работает этот тип обслуживания следующим образом: сначала обрабатывается не более N1 заявок самого высокого приоритета. Затем не более N2 заявок более низкого приоритета. Затем не более N3 заявок еще более низкого приоритета и т.д. Если в процессе обработке квоты заявок приоритета i появляется более приоритетная заявка, то обработка заявок приоритета i не прерывается, пока квота не будет выбрана, после чего пойдет обслуживание квоты для приоритета (i-1).

Такой тип обслуживания выгоден, когда нужно обеспечить условия для совокупного прогресса в обслуживании разных типов клиентов. Например, у вас могут быть клиенты-читатели, которых большинство. И клиенты-писатели, которых меньше, но которым нужно отдавать предпочтение. Скажем, клиенты-писатели размещают новости, а клиенты-читатели хотят получать наиболее свежие новости. Если вообще не выполнять запросы читателей пока работают писатели, то читателей можно заблокировать полностью, т.к. поток запросов на запись может оказаться хоть и небольшим, но непрерывным.

А вот если разделить писателей и читателей по приоритетам, и дать каждому приоритету свою квоту, то можно обеспечить разумный компромисс между внесением новой информации и раздачей уже имеющейся.

Другой сценарий, так же связанный с обслуживанием клиентов, это наличие клиентов разной важности. Скажем, платные подписчики и халявщики. С одной стороны, вам нужно с максимальным приоритетом обслуживать запросы платных подписчиков, т.к. за их счет вы живете. С другой стороны, часть халявщиков, при должном их обслуживании, со временем перейдет на платную основу. Поэтому блокировать работу с такими клиентами полностью так же нельзя. И здесь назначение разным типам клиентов разных приоритетов и введение квот может помочь найти разумный компромисс.


Третий тип обслуживания: по отдельной рабочей нити на каждый приоритет.

Как уже говорилось в прошлый раз, такой тип обслуживания довольно неоднозначен и использовать его нужно весьма осторожно. Однако, и он может найти свое применение, особенно, если для обработки события нужно иметь отдельный рабочий контекст.

Предположим, что есть необходимость обрабатывать последовательность изображений. Ну, скажем, вставлять в изображения "водяные знаки". Или применять к изображениям какой-то фильтр, скажем, имитировать старые, поцарапанные и надорванные фотографии. Изображения могут быть разных размеров и, соответственно, маленькие изображения обрабатываются быстрее, а большие -- медленнее.

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


Сейчас в SObjectizer идут работы по реализации диспетчера, который реализует первый тип обслуживания: общая рабочая нить с блокировкой низкоприоритетных событий. Однако, по мере реализации появляется соблазн сделать еще два диспетчера, для реализации двух оставшихся типов обслуживания. Возможно, именно по этому пути и пойдет развитие. Особенно, если последует положительная реакция от читателей этой и предыдущей статей ;)

Комментариев нет: