UNIX: взаимодействие процессов - Уильям Стивенс
Шрифт:
Интервал:
Закладка:
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 делает попытку заблокировать взаимное исключение (ее выполнение приостанавливается до тех пор, пока основной поток не разблокирует это взаимное исключение), а затем разблокирует его. Мы могли бы добавить условную переменную и передавать по ней уведомление, но здесь это не нужно, поскольку мы заранее знаем, в каком порядке будут происходить вызовы.