UNIX: разработка сетевых приложений
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, выходят из состояния ожидания при появлении нового клиентского соединения). Для того чтобы каждое соединение передавалось только одному потоку, необходима некая разновидность взаимного исключения, и оказывается, что быстрее это делают сами потоки, а не ядро.
30.12. Сервер с предварительным порождением потоков: основной поток вызывает функцию accept
Последняя рассматриваемая нами версия сервера устроена следующим образом: главный поток создает пул потоков при запуске сервера, после чего он же вызывает функцию
и передает каждое клиентское соединение какому-либо из свободных на данный момент потоков. Это аналогично передаче дескриптора в версии, рассмотренной нами в разделе 30.9.acceptПри таком устройстве сервера необходимо решить, каким именно образом должна осуществляться передача присоединенного дескриптора одному из потоков в пуле. Существует несколько способов решения этой задачи. Можно, как и прежде, использовать передачу дескриптора, но при этом не требуется передавать дескриптор от одного потокам к другому, так как все они, в том числе и главный поток, принадлежат одному и тому же процессу. Все, что требуется знать потоку, получающему дескриптор, — это номер дескриптора. В листинге 30.24 показан заголовочный файл
, определяющий структуруpthread08.h, аналогичный файлу, показанному в листинге 30.21.ThreadЛистинг 30.24. Заголовочный файл pthread08.h
//server/pthread08.h1 typedef struct {2 pthread_t thread_tid; /* идентификатор потока */3 long thread_count; /* количество обработанных запросов */4 } Thread;5 Thread *tptr; /* массив структур Thread */6 #define MAXNCLI 327 int clifd[MAXNCLI], iget, iput;8 pthread_mutex_t clifd_mutex;9 pthread_cond_t clifd_cond;Определение массива для записи дескрипторов присоединенных сокетов6-9 Мы определяем массив
, в который главный поток записывает дескрипторы присоединенных сокетов. Свободные потоки из пула получают по одному дескриптору из этого массива и обрабатывают соответствующий запрос,clifd— это индекс в данном массиве для очередного элемента, записываемого в него главным потоком, aiput— это индекс очередного элемента массива, передаваемого свободному потоку для обработки. Разумеется, эта структура данных, совместно используемая всеми потоками, должна быть защищена, и поэтому мы используем условную переменную и взаимное исключение.igetВ листинге 30.25 показана функция
.mainЛистинг 30.25. Функция main для сервера с предварительным порождением потоков
//server/serv08.c1 #include "unpthread.h"2 #include "pthread08.h"3 static int nthreads;4 pthread_mutex_t clifd_mutex = PTHREAD_MUTEX_INITIALIZER;5 pthread_cond_t clifd_cond = PTHREAD_COND_INITIALIZER;6 int7 main(int argc, char **argv)8 {9 int i, listenfd, connfd;10 void sig_int(int), thread_make(int);11 socklen_t addrlen, clilen;12 struct sockaddr *cliaddr;13 if (argc == 3)14 listenfd = Tcp_listen(NULL, argv[1], &addrlen);15 else if (argc == 4)16 listenfd = Tcp_listen(argv[1], argv[2], &addrlen);17 else18 err_quit("usage: serv08 [ <host> ] <port#> <#threads>");