UNIX: разработка сетевых приложений
Шрифт:
Мы также объявляем несколько глобальных переменных, таких как дескриптор прослушиваемого сокета и взаимное исключение, которые должны совместно использоваться всеми потоками.
В листинге 30.22 показана функция
Листинг 30.22. Функция main для сервера TCP с предварительным порождением потоков
Функции
Листинг 30.23. Функции thread_make и thread_main
Сравнивая строки 6 и 7 в табл. 30.1, можно заметить, что эта последняя версия нашего сервера быстрее, чем версия с созданием нового потока для каждого клиентского запроса. Этого можно было ожидать, так как в данной версии мы сразу создаем пул потоков и не тратим время на создание новых потоков по мере поступления клиентских запросов. На самом деле эта версия сервера — самая быстродействующая для всех операционных систем, которые мы испытывали.
В табл. 30.2 показано распределение значений счетчика
В Беркли-ядрах нам не нужна блокировка при вызове функции accept, так что мы можем использовать версию, представленную в листинге 30.23, без взаимных исключений. Но в результате этого время, затрачиваемое центральным процессором, увеличится. Если рассмотреть два компонента, из которых складывается время центрального процессора — пользовательское и системное время — то окажется, что первый компонент уменьшается при отсутствии блокировки (поскольку блокирование осуществляется в библиотеке потоков, входящей в пользовательское пространство), но системное время возрастает (за счет эффекта «общей побудки», возникающего, когда все потоки, блокированные в вызове функции accept, выходят из состояния ожидания при появлении нового клиентского соединения). Для того чтобы каждое соединение передавалось только одному потоку, необходима некая разновидность взаимного исключения, и оказывается, что быстрее это делают сами потоки, а не ядро.
30.12. Сервер с предварительным порождением потоков: основной поток вызывает функцию accept
Последняя рассматриваемая нами версия сервера устроена следующим образом: главный поток создает пул потоков при запуске сервера, после чего он же вызывает функцию
При таком устройстве сервера необходимо решить, каким именно образом должна осуществляться передача присоединенного дескриптора одному из потоков в пуле. Существует несколько способов решения этой задачи. Можно, как и прежде, использовать передачу дескриптора, но при этом не требуется передавать дескриптор от одного потокам к другому, так как все они, в том числе и главный поток, принадлежат одному и тому же процессу. Все, что требуется знать потоку, получающему дескриптор, — это номер дескриптора. В листинге 30.24 показан заголовочный файл
Листинг 30.24. Заголовочный файл pthread08.h