UNIX: разработка сетевых приложений
19 cliaddr = Malloc(addrlen);20 nthreads = atoi(argv[argc - 1]);21 tptr = Calloc(nthreads, sizeof(Thread));22 iget = iput = 0;23 /* создание всех потоков */24 for (i = 0; i < nthreads; i++)25 thread_make(i); /* завершается только основной поток */26 Signal(SIGINT, sig_int);27 for (;;) {28 clilen = addrlen;29 connfd = Accept(listenfd, cliaddr, &clilen);30 Pthread_mutex_lock(&clifd_mutex);31 clifd[iput] = connfd;32 if (++iput == MAXNCLI)33 iput = 0;34 if (iput == iget)35 err_quit("iput = iget = %d", iput);36 Pthread_cond_signal(&clifd_cond);37 Pthread_mutex_unlock(&clifd_mutex);38 }39 }Создание пула потоковФункция23-25создает все потоки.thread_makeОжидание прихода клиентского соединенияОсновной поток блокируется в вызове функции27-38, ожидая появления нового соединения. При появлении этого соединения дескриптор присоединенного сокета записывается в следующий элемент массиваacceptпосле блокирования взаимного исключения. Мы также следим, чтобы индексclifdне совпал со значением индексаiget, что укажет на недостаточно большой размер массива. Условная переменная сигнализирует о прибытии нового запроса, и взаимное исключение разблокируется, позволяя одному из потоков пула обслужить прибывший запрос.iputФункции
иthread_makeпоказаны в листинге 30.26. Первая из них идентична функции, приведенной в листинге 30.23.thread_mainЛистинг 30.26. Функции thread_make и thread_main
//server/pthread08.c1 #include "unpthread.h"2 #include "pthread08.h"3 void4 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 printf("thread %d starting\n", (int)arg);16 for (;;) {17 Pthread_mutex_lock(&clifd_mutex);18 while (iget == iput)19 Pthread_cond_wait(&clifd_cond, &clifd_mutex);20 connfd = clifd[iget]; /* присоединенный сокет, который требуетсяобслужить */21 if (++iget == MAXNCLI)22 iget = 0;23 Pthread_mutex_unlock(&clifd_mutex);24 tptr[(int)arg].thread_count++;25 web_child(connfd); /* обработка запроса */26 Close(connfd);27 }28 }Ожидание присоединенного сокета, который требует обслуживанияКаждый поток из пула пытается блокировать взаимное исключение, блокирующее доступ к массиву17-26. Если после того, как взаимное исключение заблокировано, оказывается, что индексыclifdиiputравны, то вызывается функцияiget, и поток переходит в состояние ожидания, так как ему пока нечего делать. После прибытия очередного клиентского запроса основной поток вызывает функциюpthread_cond_wait, выводя тем самым из состояния ожидания поток, заблокировавший взаимное исключение. Когда этот поток получает соединение, он вызывает функциюpthread_cond_signal.web_childЗначения времени центрального процессора, приведенные в табл. 30.1, показывают, что эта версия сервера медленнее рассмотренной в предыдущем разделе (когда каждый поток из пула сам вызывал функцию
). Причина заключается в том, что рассматриваемая в данном разделе версия использует как взаимное исключение, так и условную переменную, тогда как в предыдущем случае (см. листинг 30.23) применялось только взаимное исключение.acceptЕсли мы рассмотрим гистограмму количества клиентов, обслуживаемых каждым потоком из пула, то окажется, что распределение клиентских запросов по потокам будет таким же, как показано в последнем столбце табл. 30.2. Это означает, что если основной поток вызывает функцию
, то при выборе очередного потока, который будет выведен из состояния ожидания для обслуживания клиентского запроса, осуществляется последовательный перебор всех имеющихся свободных потоков.pthread_cond_signal30.13. Резюме
В этой главе мы рассмотрели 9 различных версий сервера и их работу с одним и тем же веб-клиентом, чтобы сравнить значения времени центрального процессора, затраченного на управление процессом.
0. Последовательный сервер (точка отсчета — управление процессом отсутствует).
1. Параллельный сервер, по одному вызову функции
для каждого клиента.fork