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
Последняя рассматриваемая нами версия сервера устроена следующим образом: главный поток создает пул потоков при запуске сервера, после чего он же вызывает функцию accept
и передает каждое клиентское соединение какому-либо из свободных на данный момент потоков. Это аналогично передаче дескриптора в версии, рассмотренной нами в разделе 30.9.
При таком устройстве сервера необходимо решить, каким именно образом должна осуществляться передача присоединенного дескриптора одному из потоков в пуле. Существует несколько способов решения этой задачи. Можно, как и прежде, использовать передачу дескриптора, но при этом не требуется передавать дескриптор от одного потокам к другому, так как все они, в том числе и главный поток, принадлежат одному и тому же процессу. Все, что требуется знать потоку, получающему дескриптор, — это номер дескриптора. В листинге 30.24 показан заголовочный файл pthread08.h
, определяющий структуру Thread
, аналогичный файлу, показанному в листинге 30.21.
Листинг 30.24. Заголовочный файл pthread08.h
//server/pthread08.h
1 typedef struct {
2 pthread_t thread_tid; /* идентификатор потока */
3 long thread_count; /* количество обработанных запросов */
4 } Thread;
5 Thread *tptr; /* массив структур Thread */
6 #define MAXNCLI 32
7 int clifd[MAXNCLI], iget, iput;
8 pthread_mutex_t clifd_mutex;
9 pthread_cond_t clifd_cond;
Вильям Л Саймон , Вильям Саймон , Наталья Владимировна Макеева , Нора Робертс , Юрий Викторович Щербатых
Зарубежная компьютерная, околокомпьютерная литература / ОС и Сети, интернет / Короткие любовные романы / Психология / Прочая справочная литература / Образование и наука / Книги по IT / Словари и Энциклопедии