Книга: UNIX: взаимодействие процессов

15.9. Функция door server_create

15.9. Функция door server_create

В листинге 15.6 мы показали, что библиотека дверей автоматически создает новые потоки для обслуживания запросов клиентов по мере их поступления. Они создаются библиотекой как неприсоединенные потоки (detached threads) с размером стека потока по умолчанию, с отключенной возможностью отмены потока (thread cancellation) и с маской сигналов и классом планирования (scheduling class), унаследованными от потока, вызвавшего door_create. Если мы хотим изменить какой-либо из этих параметров или хотим самостоятельно работать с пулом потоков сервера, можно воспользоваться функцией door_server_create и указать нашу собственную процедуру создания сервера:

#include <door.h>
typedef void Door_create_proc(door_info_t *);
Door_create_proc *door_server_create(Door_create_proc *proc);
/* Возвращает указатель на предыдущую процедуру создания сервера */
 

Как и при объявлении door_create в разделе 15.3, мы используем оператор typedef для упрощения прототипа библиотечной функции. Наш новый тип данных определяет процедуру создания сервера как принимающую один аргумент (указатель на структуру типа door_info_t) и ничего не возвращающую (void). При вызове door_server_create аргументом является указатель на нашу процедуру создания сервера, а возвращается указатель на предыдущую процедуру создания сервера. 

Наша процедура создания сервера вызывается при возникновении необходимости создания нового потока для обслуживания запроса клиента. Информация о том, какой из процедур сервера требуется новый поток, передается в структуре door_info_t, адрес которой принимается процедурой создания сервера. Поле di_proc содержит адрес процедуры сервера, а поле di_data содержит указатель на аргументы, передаваемые процедуре сервера при вызове.

Проще всего изучить происходящее на примере. Программа-клиент не претерпевает никаких изменений по сравнению с листингом 15.1. В программу-сервер добавляются две новые функции помимо процедуры сервера и функции main. На рис. 15.5 приведена схема сервера с четырьмя функциями и последовательностью их регистрации и вызова.


Рис. 15.5. Четыре функции в процессе-сервере

В листинге 15.17 приведен текст функции main сервера.

Листинг 15.17. Функция main для примера с управлением пулом потоков

//doors/server6.c
42 int
43 main(int argc, char **argv)
44 {
45  if (argc != 2)
46  err_quit("usage: server6 <server-pathname>");
47  Door_server_create(my_create);
48  /* создание дескриптора двери и связывание его с именем */
49  Pthread_mutex_lock(&fdlock);
50  fd = Door_create(servproc, NULL, DOOR_PRIVATE);
51  Pthread_mutex_unlock(&fdlock);
52  unlink(argv[1]);
53  Close(Open(argv[1], O_CREAT | O_RDWR, FILE_MODE));
54  Fattach(fd, argv[1]);
55  /* servproc() обслуживает запросы клиентов */
56  for(;;)
57   pause();
58 }

По сравнению с листингом 15.2 было внесено четыре изменения:

1. Убрано объявление дескриптора двери fd (теперь это глобальная переменная, описанная в листинге 15.18).

2. Вызов door_create защищен взаимным исключением (также описанным в листинге 15.18).

3. Вызов door_server_create делается перед созданием двери, при этом указывается процедура создания сервера (my_thread, которая, будет показана позже).

4. В вызове door_create последний аргумент (атрибуты) имеет значение DOOR_PRIVATE вместо 0. Это говорит библиотеке о том, что данная дверь будет иметь собственный пул потоков, называемый частным пулом сервера.

Задание процедуры создания сервера с помощью door_server_create и выделение частного пула сервера с помощью DOOR_PRIVATE осуществляются независимо друг от друга. Возможны четыре ситуации:

1. По умолчанию частный пул сервера и процедура создания сервера отсутствуют. Система создает потоки по мере необходимости и они переходят в пул потоков процесса.

2. Указан флаг DOOR_PRIVATE, но процедура создания сервера отсутствует. Система создает потоки по мере необходимости и они отходят в пул потоков процесса, если относятся к тем дверям, для которых флаг DOOR_PRIVATE не был указан, либо в пул данной двери, если она была создана с флагом DOOR_PRIVATE.

3. Отсутствует частный пул сервера, но указана процедура создания сервера. Процедура создания вызывается при необходимости создания нового потока, который затем переходит в пул потоков процесса.

4. Указан флаг DOOR_PRIVATE и процедура создания сервера. Процедура создания сервера вызывается каждый раз при необходимости создания потока. После создания поток должен вызвать door_bind для отнесения его к нужному частному пулу сервера, иначе он будет добавлен к пулу потоков процесса.

В листинге 15.18 приведен текст двух новых функций: my_create (процедура создания сервера) и my_thread (функция, выполняемая каждым потоком, который создается my_create).

Листинг 15.18. Функции управления потоками

//doors/server6.c
13 pthread_mutex_t fdlock = PTHREAD_MUTEX_INITIALIZER;
14 static int fd = –1; /* дескриптор двери */
15 void *
16 my_thread(void *arg)
17 {
18  int oldstate;
19  door_info_t *iptr = arg;
20  if ((Door_server_proc*)iptr->di_proc == servproc) {
21   Pthread_mutex_lock(&fdlock);
22   Pthread_mutex_unlock(&fdlock);
23   Pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
24   Door_bind(fd);
25   Door_return(NULL, 0, NULL, 0);
26  } else
27   err_quit("my_thread: unknown function: %p", arg);
28  return(NULL); /* никогда не выполняется */
29 }
30 void
31 my_create(door info_t *iptr)
32 {
33  pthread_t tid;
34  pthread_attr_t attr;
35  Pthread_attr_init(&attr);
36  Pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
37  Pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
38  Pthread_create(&tid, &attr, my_thread, (void *)iptr);
39  Pthread_attr_destroy(&attr);
40  printf("my_thread: created server thread %ldn", pr_thread_id(&tid)):
41 }

Процедура создания сервера

30-41 Каждый раз при вызове my_create создается новый поток. Перед вызовом pthread_create атрибуты потока инициализируются, область потока устанавливается равной PTHREAD_SCOPE_SYSTEM и поток определяется как неприсоединенный (detached). Созданный поток вызывает функцию my_thread. Аргументом этой функции является указатель на структуру типа door_info_t. Если у нас имеется сервер с несколькими дверьми и мы указываем процедуру создания сервера, эта процедура создания сервера будет вызываться при необходимости создания потока для любой из дверей. Единственный способ, которым эта процедура может определить тип сервера, соответствующий нужной двери, заключается в изучении указателя di_proc в структуре типа door_info_t.

ПРИМЕЧАНИЕ

Установка области выполнения PTHREAD_SCOPE_SYSTEM означает, что поток будет конкурировать в распределении ресурсов процессора с другими потоками системы. Альтернативой является указание PTHREAD_SCOPE_PROCESS; при этом поток будет конкурировать только с другими потоками данного процесса. Последнее не будет работать с дверьми, поскольку библиотека дверей требует, чтобы тот процесс ядра, который привел к вызову данного потока, выполнял и door_return. Поток с PTHREAD_SCOPE_PROCESS может сменить поток ядра во время выполнения процедуры сервера.

Причина, по которой поток должен создаваться как неприсоединенный, заключается в том, что нужно предотвратить сохранение в системе информации о потоке после его завершения, потому что отсутствует поток, вызывающий pthread_join.

Функция, запускающая поток

15-20 При создании потока запускается функция my_thread, указанная в вызове pthread_create. Аргументом является указатель на структуру типа door_info_t, передаваемый my_create. В данном примере есть только одна процедура сервера — servproc, и мы просто проверяем, что аргумент указывает на эту процедуру.

Ожидание присваивания дескриптору правильного значения

21-22 Процедура создания сервера вызывается в первый раз при вызове door_create для создания первого потока сервера. Этот вызов осуществляется из библиотеки дверей до завершения работы door_create. Однако переменная fd не примет значения дескриптора двери до тех пор, пока не произойдет возврата из функции door_create (проблема курицы и яйца). Поскольку мы знаем, что my_thread выполняется отдельно от основного потока, решение состоит в том, чтобы использовать взаимное исключение fdlock следующим образом: основной поток блокирует взаимное исключение перед вызовом door_create и разблокирует после возврата из door_create (когда дескриптору fd уже присвоено некоторое значение). Функция my_thread делает попытку заблокировать взаимное исключение (ее выполнение приостанавливается до тех пор, пока основной поток не разблокирует это взаимное исключение), а затем разблокирует его. Мы могли бы добавить условную переменную и передавать по ней уведомление, но здесь это не нужно, поскольку мы заранее знаем, в каком порядке будут происходить вызовы.

Отключение отмены потока

23 При создании нового потока вызовом pthread_create его отмена по умолчанию разрешена. Если отмена потока разрешена и клиент прерывает вызов door_call в процессе его выполнения (что мы продемонстрируем в листинге 15.26), вызываются обработчики отмены потока, после чего он завершается. Если отмена потока отключена (как это делаем мы) и клиент прерывает работу в вызове door_call, процедура сервера спокойно завершает работу (поток не завершается), а результаты door_return просто сбрасываются. Поскольку серверный поток завершается, если происходит отмена потока, и поскольку процедура сервера может в этот момент выполнять какие-то действия (возможно, с заблокированными семафорами или блокировками), библиотека дверей на всякий случай отключает отмену всех создаваемых ею потоков. Если нам нужно, чтобы процедура сервера отменялась при досрочном завершении работы клиента, для этого потока следует включить возможность отмены и приготовиться обработать такую ситуацию.

ПРИМЕЧАНИЕ

Обратите внимание, что область выполнения PTHREAD_SCOPE_SYSTEM и неприсоединенность потока указываются как атрибуты при создании потока. А отмена потока может быть отключена только в процессе выполнения потока. Таким образом, хотя мы и отключаем отмену потока, он сам может ее включить и выключить тогда, когда потребуется.

Связывание потока с дверью

24 Вызов door_bind позволяет добавить поток к пулу, связанному с дверью, дескриптор которой передается door_bind в качестве аргумента. Поскольку для этого нам нужно знать дескриптор двери, в этой версии сервера он является глобальной переменной.

Делаем поток доступным клиенту

25 Мы делаем поток доступным клиенту вызовом door_return с двумя нулевыми указателями и нулевыми значениями длин буферов в качестве аргументов.

Процедура сервера приведена в листинге 15.19. Она идентична программе из листинга 15.6.

Листинг 15.19. Процедура сервера

//doors/server6.c
1  #include "unpipc.h"
2  void
3  servproc(void *cookie, char *dataptr, size_t datasize,
4   door_desc_t *descptr, size_t ndesc)
5  {
6   long arg, result;
7   arg = *((long *) dataptr);
8   printf("thread id %ld, arg = %ldn", pr_thread_id(NULL), arg);
9   sleep(5);
10  result = arg * arg;
11  Door_return((char *)&result, sizeof(result), NULL, 0);
12 }

Чтобы продемонстрировать работу программы, запустим сервер:

solaris % server6 /tmp/door6
my_thread: created server thread 4

После запуска сервера и вызова door_create процедура создания сервера запускается в первый раз, хотя клиент мы еще не запустили. При этом создается первый поток, ожидающий запроса от первого клиента. Затем мы запускаем клиент три раза подряд:

solaris % client6 /tmp/door6 11
result: 121
solaris % client6 /tmp/door6 22
result: 484
solaris % client6 /tmp/door6 33
result: 1089

Посмотрим, что при этом выводит сервер. При поступлении первого запроса клиента создается новый поток (с идентификатором потока 5), а поток с номером 4 обслуживает все запросы клиентов. Библиотека дверей всегда держит один лишний поток наготове:

my_thread: created server thread 5
thread id 4, arg = 11
thread id 4, arg = 22
thread id 4, arg = 33

Запустим теперь три экземпляра клиента одновременно в фоновом режиме:

solaris % client6 /tmp/door6 44 &client6 /tmp/door6 55 &client6 /tmp/door6 66 &
[2] 4919
[3] 4920
[4] 4921
solaris % result: 1936
result: 4356
result: 3025

Посмотрев на вывод сервера, мы увидим, что было создано два новых потока (с идентификаторами 6 и 7) и потоки 4, 5 и 6 обслужили три запроса от клиентов:

thread id 4, arg = 44
my_thread: created server thread 6
thread id 5, arg = 66
my_thread: created server thread 7
thread id 6, arg = 55

Оглавление книги


Генерация: 0.371. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз