Теперь, когда контекст создан функцией context_alloc() , вызывается функция block_func() для перевода потока в режим блокирования. Заметьте, что функция block_func() получает на вход результат работы функции context_alloc() . После того как функция block_func() разблокируется, она возвращает указатель на контекст, который библиотека передает функции handler_func() . Функция handler_func() отвечает за выполнение «работы» — например, в типовом варианте именно она обрабатывает сообщение от клиента. На данный момент принято, что функция handler_func() должна возвращать нуль — ненулевые значения зарезервированы QSSL для будущего функционального расширения. Функция unblock_func() также в настоящее время зарезервирована, поэтому просто оставьте там NULL.
Возможно, ситуацию немного прояснит приведенный ниже пример псевдокода (он основан все на том же рисунке «Жизненный цикл потока в пуле потоков»):
FOREVER DO
IF (#threads < lo_water) THEN
IF (#threads < maximum) THEN
create new thread
context = (*context_alloc)(handle);
ENDIF
ENDIF
retval = (*block_func)(context);
(*handler_func)(retval);
IF (#threads > hi_water) THEN
(*context_free)(context)
kill thread
ENDIF
DONE
Отметим, что приведенная выше программа излишне упрощена. Ее назначение состоит только в том, чтобы продемонстрировать вам поток данных по параметрам ctp и handler и дать вам некоторое представление об алгоритмах, которые обычно применяются для управления числом потоков.
Диспетчеризация и реальный мир
До настоящего момента мы обсуждали дисциплины диспетчеризации и состояния потоков, но практически ничего не сказали относительно того, почему и когда происходит собственно перепланирование. Существует распространенное заблуждение, что перепланирование «просто случается», безо всяких реальных причин. И в общем-то, для проектирования это довольно полезная абстракция! Однако, очень важно понимать, почему происходит перепланирование. Вспомним рисунок «Схема алгоритма диспетчеризации» (в разделе «Роль ядра»).
Перепланирование может иметь только три причины:
• аппаратное прерывание;
• системный вызов;
• сбой (исключение).
Перепланирование по аппаратному прерыванию
Перепланирование из-за аппаратного прерывания можно разделить на две категории:
• по прерыванию от таймеров;
• по прерыванию от других аппаратных средств.
Часы реального времени генерируют периодические прерывания для ядра, организуя перепланирование во времени.
Например, если вы производите вызов sleep(10)
, часы реального времени сгенерируют некоторое число прерываний; по каждому прерыванию ядро увеличивает значение системных часов. Когда системные часы покажут, что 10 секунд истекли, ядро перепланирует ваш поток, переведя его в состояние готовности (READY). (Мы рассмотрим этот вопрос более подробно в главе «Часы, таймеры и периодические уведомления»).
Другие потоки могут ожидать аппаратные прерывания от внешних устройств, таких как последовательный порт, жесткий диск или аудио платы. В этом случае они блокируются в ядре, ожидающем аппаратное прерывание. Поток будет переупорядочен ядром только после того, как ядро сгенерирует «событие».
Перепланирование по системным вызовам
Если поток делает системный вызов, перепланирование выполняется немедленно и может рассматриваться как асинхронное в отношении прерываний таймера и других прерываний.
Например, выше мы приводили пример вызова функции sleep(10)
. Это библиотечная функция языка Си, в конечном счете она транслируется в системный вызов. В тот же самый момент ядро приняло решение о перепланировании, чтобы удалить ваш поток из очереди готовности по соответствующему приоритету и поставить на выполнение другой поток, находящийся в состоянии готовности (READY).
Системных вызов, вызывающи процесс обязательного перепланирования, очень много. Большинство их них достаточно очевидны. Перечислим некоторые из них:
• функции таймера (например, sleep() );
• функции обмена сообщениями (например, MsgSendv() );
• примитивы работы с потоками (например, pthread_cancel() или pthread_join() ).
Перепланирование по исключительным ситуациям
Последняя из вышеперечисленных причин перепланирования — это сбой процессора (CPU fault), который является исключительной ситуацией (exception) — чем-то средним между аппаратным прерыванием и системным вызовом. Исключительные ситуации асинхронны в отношении ядра (подобно прерыванию), но синхронны с вызывающими их пользовательскими программами (подобно вызову ядра — например, такая исключительная ситуация как деление на ноль). Все рассуждения, относящиеся к перепланированию по прерываниям от аппаратных средств и по системным вызовам, относятся и к исключительным ситуациям тоже.
Читать дальше