30.11. Сервер TCP с предварительным порождением потоков, каждый из которых вызывает accept

30.11. Сервер TCP с предварительным порождением потоков, каждый из которых вызывает accept

Ранее в этой главе мы обнаружили, что версии, в которых заранее создается пул дочерних процессов, работают быстрее, чем те, в которых для каждого клиентского запроса приходится вызывать функцию fork. Для систем, поддерживающих потоки, логично предположить, что имеется та же закономерность: быстрее сразу создать пул потоков при запуске сервера, чем создавать по одному потоку по мере поступления запросов от клиентов. Основная идея такого сервера заключается в том, чтобы создать пул потоков, каждый из которых вызывает затем функцию accept. Вместо того чтобы блокировать потоки в вызове accept, мы используем взаимное исключение, как в разделе 30.8. Это позволяет вызывать функцию accept только одному потоку в каждый момент времени. Использовать блокировку файла для защиты accept в таком случае бессмысленно, так как при наличии нескольких потоков внутри данного процесса можно использовать взаимное исключение.

В листинге 30.21 показан заголовочный файл pthread07.h, определяющий структуру Thread, содержащую определенную информацию о каждом потоке.

Листинг 30.21. Заголовочный файл pthread07.h

//server/pthread07.h

1 typedef struct {

2  pthread_t thread_tid; /* идентификатор потока */

3  long thread_count; /* количество обработанных запросов */

4 } Thread;

5 Thread *tptr; /* массив структур Thread */

6 int listenfd, nthreads;

7 socklen_t addrlen;

8 pthread_mutex_t mlock;

Мы также объявляем несколько глобальных переменных, таких как дескриптор прослушиваемого сокета и взаимное исключение, которые должны совместно использоваться всеми потоками.

В листинге 30.22 показана функция main.

Листинг 30.22. Функция main для сервера TCP с предварительным порождением потоков

//server/serv07.c

 1 #include "unpthread.h"

 2 #include "pthread07.h"

 3 pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER;

 4 int

 5 main(int argc, char **argv)

 6 {

 7  int i;

 8  void sig_int(int), thread_make(int);

 9  if (argc == 3)

10   listenfd = Tcp_listen(NULL, argv[1], &addrlen);

11  else if (argc == 4)

12   listenfd = Tcp_1isten(argv[1], argv[2], &addrlen);

13  else

14   err_quit("usage: serv07 [ <host> ] <port#> <#threads>");

15  nthreads = atoi(argv[argc - 1]);

16  tptr = Calloc(nthreads, sizeof(Thread));

17  for (i = 0; i < nthreads; i++)

18  thread_make(i); /* завершается только основной поток */

19  Signal(SIGINT, sig_int);

20  for (;;)

21   pause(); /* потоки все выполнили */

22 }

Функции thread_make и thread_main показаны в листинге 30.23.

Листинг 30.23. Функции thread_make и thread_main

//server/pthread07.c

 1 #include "unpthread.h"

 2 #include "pthread07.h"

 3 void

 4 thread_make(int i)

 5 {

 6  void *thread_main(void*);

 7  Pthread_create(&tptr[i].thread_tid, NULL, &thread_main, (void*)i);

 8  return; /* завершается основной поток */

 9 }

10 void*

11 thread_main(void *arg)

12 {

13  int connfd;

14  void web_child(int);

15  socklen_t clilen;

16  struct sockaddr *cliaddr;

17  cliaddr = Malloc(addrlen);

18  printf("thread %d starting ", (int)arg);

19  for (;;) {

20   clilen = addrlen;

21   Pthread_mutex_lock(&mlock);

22   connfd = Accept(listenfd, cliaddr, &clilen);

23   Pthread_mutex_unlock(&mlock);

24   tptr[(int)arg].thread_count++;

25   web_child(connfd); /* обработка запроса */

26   Close(connfd);

27  }

28 }

Создание потоков

7 Создаются потоки, каждый из которых выполняет функцию pthread_main. Единственным аргументом этой функции является порядковый номер потока.

21-23 Функция thread_main вызывает функции pthread_mutex_lock и pthread_mutex_unlock соответственно до и после вызова функции accept.

Сравнивая строки 6 и 7 в табл. 30.1, можно заметить, что эта последняя версия нашего сервера быстрее, чем версия с созданием нового потока для каждого клиентского запроса. Этого можно было ожидать, так как в данной версии мы сразу создаем пул потоков и не тратим время на создание новых потоков по мере поступления клиентских запросов. На самом деле эта версия сервера — самая быстродействующая для всех операционных систем, которые мы испытывали.

В табл. 30.2 показано распределение значений счетчика thread_count структуры Thread, которые мы выводим с помощью обработчика сигнала SIGINT по завершении работы сервера. Равномерность этого распределения объясняется тем, что при выборе потока, который будет блокировать взаимное исключение, алгоритм планирования загрузки потоков последовательно перебирает все потоки в цикле.

ПРИМЕЧАНИЕ

В Беркли-ядрах нам не нужна блокировка при вызове функции accept, так что мы можем использовать версию, представленную в листинге 30.23, без взаимных исключений. Но в результате этого время, затрачиваемое центральным процессором, увеличится. Если рассмотреть два компонента, из которых складывается время центрального процессора — пользовательское и системное время — то окажется, что первый компонент уменьшается при отсутствии блокировки (поскольку блокирование осуществляется в библиотеке потоков, входящей в пользовательское пространство), но системное время возрастает (за счет эффекта «общей побудки», возникающего, когда все потоки, блокированные в вызове функции accept, выходят из состояния ожидания при появлении нового клиентского соединения). Для того чтобы каждое соединение передавалось только одному потоку, необходима некая разновидность взаимного исключения, и оказывается, что быстрее это делают сами потоки, а не ядро.

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

6.5.1. Действие ACCEPT

Из книги Iptables Tutorial 1.1.19 автора Andreasson Oskar

6.5.1. Действие ACCEPT Данная операция не имеет дополнительных ключей. Если над пакетом выполняется действие ACCEPT, то пакет прекращает движение по цепочке (и всем вызвавшим цепочкам, если текущая цепочка была вложенной) и считается ПРИНЯТЫМ (то бишь пропускается), тем не менее,


Правило 5: Какие функции C++ создает и вызывает молча

Из книги Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ автора Мейерс Скотт

Правило 5: Какие функции C++ создает и вызывает молча Когда пустой класс перестает быть пустым? Когда за него берется C++. Если вы не объявите конструктор копирования, оператор присваивания или деструктор самостоятельно, то компилятор сделает это за вас. Более того, если вы


Стеки потоков и допустимые количества потоков

Из книги Системное программирование в среде Windows автора Харт Джонсон М

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


Каждый день

Из книги Scrum и XP: заметки с передовой автора Книберг Хенрик

Каждый день • Следить за тем, чтобы ежедневный Scrum начинался и заканчивался вовремя.• Следить за тем, чтобы в случае добавления или удаления истории из sprint backlog’а все было сделано, как положено, чтобы эти изменения не сорвали график работ.a) Следить за тем, чтобы product owner


16.14. Сервер kHTTPd — веб-сервер уровня ядра

Из книги Linux: Полное руководство автора Колисниченко Денис Николаевич

16.14. Сервер kHTTPd — веб-сервер уровня ядра В операционной системе все процессы можно разделить на два типа: процессы уровня ядра и пользовательские процессы. Процесс уровня ядра запускается и работает очень быстро по сравнению с относительно неповоротливым


Пример 33-6. Сценарий (бесполезный), который вызывает себя сам

Из книги Искусство программирования на языке сценариев командной оболочки автора Купер Мендель

Пример 33-6. Сценарий (бесполезный), который вызывает себя сам #!/bin/bash# recurse.sh# Может ли сценарий вызвать себя сам?# Да, но есть ли в этом смысл?RANGE=10MAXVAL=9i=$RANDOMlet "i %= $RANGE" # Генерация псевдослучайного числа в диапазоне 0 .. $MAXVAL.if [ "$i" -lt "$MAXVAL" ]then echo "i = $i" ./$0 # Сценарий запускает


Пример 33-7. Сценарий имеющий практическую ценность), который вызывает себя сам

Из книги UNIX: разработка сетевых приложений автора Стивенс Уильям Ричард

Пример 33-7. Сценарий имеющий практическую ценность), который вызывает себя сам #!/bin/bash# pb.sh: телефонная книга# Автор: Rick Boivie# используется с его разрешения.# Дополнен автором документа.MINARGS=1 # Сценарию должен быть передан, по меньшей мере, один


4.6. Функция accept

Из книги автора

4.6. Функция accept Функция accept вызывается сервером TCP для возвращения следующего установленного соединения из начала очереди полностью установленных соединений (см. рис. 4.2). Если очередь полностью установленных соединений пуста, процесс переходит в состояние ожидания (по


5.11. Прерывание соединения перед завершением функции accept

Из книги автора

5.11. Прерывание соединения перед завершением функции accept Существует другое условие, аналогичное прерванному системному вызову, пример которого был описан в предыдущем разделе. Оно может привести к возвращению функцией accept нефатальной ошибки, в случае чего следует


16.6. Неблокируемая функция accept

Из книги автора

16.6. Неблокируемая функция accept Как было сказано в главе 6, функция select сообщает, что прослушиваемый сокет готов для чтения, когда установленное соединение готово к обработке функцией accept. Следовательно, если мы используем функцию select для определения готовности входящих


30.6. Сервер TCP с предварительным порождением процессов без блокировки для вызова accept

Из книги автора

30.6. Сервер TCP с предварительным порождением процессов без блокировки для вызова accept В первом из рассматриваемых нами «усовершенствованных» серверов используется технология, называемая предварительным созданием процессов (preforking). Вместо того чтобы вызывать функцию fork


30.7. Сервер TCP с предварительным порождением процессов и защитой вызова accept блокировкой файла

Из книги автора

30.7. Сервер TCP с предварительным порождением процессов и защитой вызова accept блокировкой файла Описанная выше реализация, позволяющая нескольким процессам вызывать функцию accept на одном и том же прослушиваемом дескрипторе, возможна только для систем 4.4BSD, в которых функция


30.8. Сервер TCP с предварительным порождением процессов и защитой вызова accept при помощи взаимного исключения

Из книги автора

30.8. Сервер TCP с предварительным порождением процессов и защитой вызова accept при помощи взаимного исключения Как мы уже говорили, существует несколько способов синхронизации процессов путем блокирования. Блокировка файла по стандарту POSIX, рассмотренная в предыдущем


30.9. Сервер TCP с предварительным порождением процессов: передача дескриптора

Из книги автора

30.9. Сервер TCP с предварительным порождением процессов: передача дескриптора Последней модификацией нашего сервера с предварительным порождением процессов является версия, в которой только родительский процесс вызывает функцию accept, а затем «передает» присоединенный


30.12. Сервер с предварительным порождением потоков: основной поток вызывает функцию accept

Из книги автора

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