Чтение онлайн

ЖАНРЫ

UNIX: разработка сетевых приложений
Шрифт:

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\n", (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, выходят из состояния ожидания при появлении нового клиентского соединения). Для того чтобы каждое соединение передавалось только одному потоку, необходима некая разновидность взаимного исключения, и оказывается, что быстрее это делают сами потоки, а не ядро.

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

Поделиться с друзьями: