В Беркли-ядрах нам не нужна блокировка при вызове функции 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;
6-9 Мы определяем массив
clifd
, в который главный поток записывает дескрипторы присоединенных сокетов. Свободные потоки из пула получают по одному дескриптору из этого массива и обрабатывают соответствующий запрос,
iput
— это индекс в данном массиве для очередного элемента, записываемого в него главным потоком, a
iget
— это индекс очередного элемента массива, передаваемого свободному потоку для обработки. Разумеется, эта структура данных, совместно используемая всеми потоками, должна быть защищена, и поэтому мы используем условную переменную и взаимное исключение.
В листинге 30.25 показана функция
main
.
Листинг 30.25. Функция main для сервера с предварительным порождением потоков
//server/serv08.c
1 #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 int