20 connfd = clifd[iget]; /* присоединенный сокет, который требуется
обслужить */
21 if (++iget == MAXNCLI)
22 iget = 0;
23 Pthread_mutex_unlock(&clifd_mutex);
24 tptr[(int)arg].thread_count++;
25 web_child(connfd); /* обработка запроса */
26 Close(connfd);
27 }
28 }
17-26
Каждый поток из пула пытается блокировать взаимное исключение, блокирующее доступ к массиву clifd
. Если после того, как взаимное исключение заблокировано, оказывается, что индексы iput
и iget
равны, то вызывается функция pthread_cond_wait
, и поток переходит в состояние ожидания, так как ему пока нечего делать. После прибытия очередного клиентского запроса основной поток вызывает функцию pthread_cond_signal
, выводя тем самым из состояния ожидания поток, заблокировавший взаимное исключение. Когда этот поток получает соединение, он вызывает функцию web_child
.
Значения времени центрального процессора, приведенные в табл. 30.1, показывают, что эта версия сервера медленнее рассмотренной в предыдущем разделе (когда каждый поток из пула сам вызывал функцию accept
). Причина заключается в том, что рассматриваемая в данном разделе версия использует как взаимное исключение, так и условную переменную, тогда как в предыдущем случае (см. листинг 30.23) применялось только взаимное исключение.
Если мы рассмотрим гистограмму количества клиентов, обслуживаемых каждым потоком из пула, то окажется, что распределение клиентских запросов по потокам будет таким же, как показано в последнем столбце табл. 30.2. Это означает, что если основной поток вызывает функцию pthread_cond_signal
, то при выборе очередного потока, который будет выведен из состояния ожидания для обслуживания клиентского запроса, осуществляется последовательный перебор всех имеющихся свободных потоков.
30.13. Резюме
В этой главе мы рассмотрели 9 различных версий сервера и их работу с одним и тем же веб-клиентом, чтобы сравнить значения времени центрального процессора, затраченного на управление процессом.
0. Последовательный сервер (точка отсчета — управление процессом отсутствует).
1. Параллельный сервер, по одному вызову функции fork
для каждого клиента.
2. Предварительное порождение дочерних процессов, каждый из которых вызывает функцию accept
.
3. Предварительное порождение дочерних процессов с блокировкой файла для защиты функции accept
.
4. Предварительное порождение дочерних процессов с блокировкой взаимного исключения дочерними процессами для защиты функции accept
.
5. Предварительное порождение дочерних процессов с передачей дескриптора от родительского процесса дочернему.
6. Параллельный сервер, поочередное создание потоков по мере поступления клиентских запросов.
7. Предварительное порождение потоков с блокировкой взаимного исключения потоками для защиты функции accept
.
8. Предварительное порождение потоков, основной поток вызывает функцию accept
.
Резюмируя материал этой главы, можно сделать несколько комментариев.
■ Если сервер не слишком загружен, хорошо работает традиционная модель параллельного сервера, в которой при поступлении очередного клиентского запроса вызывается функция fork
для создания нового дочернего процесса. Этот вариант допускает комбинирование с демоном inetd
, принимающим все клиентские запросы. Остальные версии применимы в случае загруженных серверов, таких как веб-серверы.
■ Создание пула дочерних процессов или потоков сокращает временные затраты центрального процессора по сравнению с традиционной моделью (один вызов функции fork
для каждого запроса) в 10 и более раз. При этом не слишком усложняется код, но становится необходимо (как говорилось при обсуждении примеров) отслеживать количество свободных дочерних процессов и корректировать его по мере необходимости, так как количество клиентских запросов, которые требуется обслужить, динамически изменяется.
■ Некоторые реализации допускают блокирование нескольких потоков или дочерних процессов в вызове функции accept
, в то время как другие реализации требуют использования блокировки того или иного типа для защиты accept
. Можно использовать для этого либо блокировку файла, либо блокировку взаимного исключения Pthreads.
■ Как правило, версия, в которой каждый поток или дочерний процесс вызывает функцию accept
, проще и быстрее, чем версия, где вызов функции accept
осуществляется только основным потоком (или родительским процессом), впоследствии передающим дескриптор присоединенного сокета другому потоку или дочернему процессу.
Вильям Л Саймон , Вильям Саймон , Наталья Владимировна Макеева , Нора Робертс , Юрий Викторович Щербатых
Зарубежная компьютерная, околокомпьютерная литература / ОС и Сети, интернет / Короткие любовные романы / Психология / Прочая справочная литература / Образование и наука / Книги по IT / Словари и Энциклопедии