Книга: UNIX: взаимодействие процессов
16.2. Многопоточность
Разделы на этой странице:
- Листинг 16.4. Процедура сервера с 5-секундной паузой
- Листинг 16.5. Функция main клиента многопоточного сервера
- Объявление переменной для помещения результата
- Новый аргумент в вызове процедуры
- Листинг 16.6. Процедура многопоточного сервера
- Новые аргументы и возвращаемое значение
- Новая функция, освобождающая память XDR
16.2. Многопоточность
Вспомните листинг 15.6, где мы продемонстрировали автоматическое управление потоками, осуществляемое библиотекой дверей. При этом сервер по умолчанию являлся параллельным. Покажем теперь, что средства Sun RPC по умолчанию делают сервер последовательным. Начнем с примера из предыдущего раздела и изменим только процедуру сервера. В листинге 16.4 приведен текст новой функции, выводящей идентификатор потока, делающей 5-секундную паузу, выводящей идентификатор еще раз и завершающей работу.
Листинг 16.4. Процедура сервера с 5-секундной паузой
//sunrpc/square2/server.c
1 #include "unpipc.h"
2 #include "square.h"
3 square_out *
4 squareproc_1_svc(square_in *inp, struct svc_req *rqstp)
5 {
6 static square_out out;
7 printf("thread %ld started, arg = %ldn",
8 pr_thread_id(NULL), inp->arg1);
9 sleep(5);
10 out.res1 = inp->arg1 * inp->arg1;
11 printf("thread %ld donen", pr_thread_id(NULL));
12 return(&out);
13 }
Запустим сервер, а после этого запустим три экземпляра программы-клиента:
solaris % client localhost 22 & client localhost 33& client localhost 44 &
[3] 25179
[4] 25180
[5] 25181
solaris % result: 484 примерно через 5 секунд после появления подсказки
result: 1936 еще через 5 секунд
result: 1089 еще через 5 секунд
Хотя этого нельзя сказать по выводимому тексту, перед появлением очередного результата проходит примерно 5 секунд. Если мы посмотрим на текст, выводимый сервером, то увидим, что клиенты обрабатываются последовательно: сначала полностью обрабатывается запрос первого клиента, затем второго и третьего:
solaris % server
thread 1 started, arg = 22
thread 1 done
thread 1 started, arg = 44
thread 1 done
thread 1 started, arg = 33
thread 1 done
Один и тот же поток обслуживает все запросы клиентов. Сервер не является многопоточным по умолчанию.
ПРИМЕЧАНИЕ
Серверы дверей в главе 15 работали не в фоновом режиме, а запускались из интерпретатора. Это давало нам возможность добавлять отладочные вызовы printf в процедуры сервера. Однако серверы Sun RPC по умолчанию являются демонами и выполняют действия так, как это описано в разделе 12.4 [24]. Это требует вызова syslog из процедуры сервера для вывода диагностической информации. Однако мы указали флаг –DDEBUG при компиляции нашего сервера, что эквивалентно определению
#define DEBUG
в заглушке сервера (файле square_svc.c, создаваемом rpcgen). Это запрещает функции main становиться демоном и оставляет ее подключенной к терминалу, в котором она была запущена. Поэтому мы можем спокойно вызывать printf из процедуры сервера.
Возможность создания многопоточного сервера появилась в Solaris 2.4 и реализуется добавлением параметра –М в строку вызова rpcgen. Это делает код, создаваемый rpcgen, защищенным. Другой параметр, –А, позволяет автоматически создавать потоки по мере необходимости для обслуживания запросов клиентов. Мы включаем оба параметра при вызове rpcgen.
Однако для реализации многопоточности требуется внести изменения в текст клиента и сервера, чего мы могли ожидать, поскольку использовали тип static в листинге 16.3. Единственное изменение, которое нужно внести в файл square.х, — сменить номер версии с 1 на 2. В объявлениях аргументов процедуры и результатов ничего не изменится.
В листинге 16.5 приведен текст новой программы-клиента.
Листинг 16.5. Функция main клиента многопоточного сервера
//sunrpc/square3/client.c
1 #include "unpipc.h"
2 #include "square.h"
3 int
4 main(int argc, char **argv)
5 {
6 CLIENT *cl;
7 square_in in;
8 square_out out;
9 if (argc != 3)
10 err_quit("usage: client <hostname> <integer-value>");
11 cl = Clnt_create(argv[1], SQUARE_PROG, SQUARE_VERS, "tcp");
12 in.arg1 = atol(argv[2]);
13 if (squareproc_2(&in, &out, cl) != RPC_SUCCESS)
14 err_quit("%s", clnt_sperror(cl, argv[1]));
15 printf("result: %ldn", out.res1);
16 exit(0);
17 }
Объявление переменной для помещения результата
8 Мы объявляем переменную типа square_out, а не указатель на нее.
Новый аргумент в вызове процедуры
12-14 Вторым аргументом вызова squareproc_2 становится указатель на переменную out, а последним аргументом является дескриптор клиента. Вместо возвращения указателя на результат (как в листинге 16.2) эта функция будет возвращать либо RPC_SUCCESS, либо некоторое другое значение в случае возникновения ошибок. Перечисление enumclnt_stat в заголовочном файле <rpc/clnt_stat.h> содержит все возможные коды ошибок.
В листинге 16.6 приведен текст новой процедуры сервера. Как и программа из листинга 16.4, эта версия выводит идентификатор потока, ждет 5 секунд, а затем завершает работу.
Листинг 16.6. Процедура многопоточного сервера
//sunrpc/square3/server.c
1 #include "unpipc.h"
2 #include "square.h"
3 bool_t
4 squareproc_2_svc(square_in *inp, square_out *outp, struct svc_req *rqstp)
5 {
6 printf("thread %Id started, arg = %ldn",
7 pr_thread_id(NULL), inp->arg1);
8 sleep(5);
9 outp->res1 = inp->arg1 * inp->arg1;
10 printf("thread %ld donen", pr_thread_id(NULL));
11 return(TRUE);
12 }
13 int
14 square_prog_2_freeresult(SVCXPRT *transp, xdrproc_t xdr_result,
15 caddr_t result)
16 {
17 xdr_free(xdr_result, result);
18 return(1);
19 }
Новые аргументы и возвращаемое значение
3-12 Требуемые для реализации многопоточности изменения включают изменение аргументов функций и возвращаемого значения. Вместо возвращения указателя на структуру результатов (как в листинге 16.3) указатель на эту структуру принимается в качестве второго аргумента функции. Указатель на структуру svc_req смещается на третью позицию. Теперь при успешном завершении функции возвращается значение TRUE, а при возникновении ошибок — FALSE.
Новая функция, освобождающая память XDR
13-19 Еще одно изменение заключается в добавлении функции, освобождающей все автоматически выделенные переменные. Эта функция вызывается из заглушки сервера после завершения работы процедуры сервера и отправки результата клиенту. В нашем примере просто делается вызов подпрограммы xdr_free (о ней будет говориться более подробно в связи с листингом 16.19 и упражнением 16.10).
Если процедура сервера выделяла память под сохраняемый результат (например, в виде связного списка), этот вызов освободит занятую память.
Создадим программу-клиент и программу-сервер и запустим три экземпляра клиента одновременно:
solaris % client localhost 55 & client localhost 66 & client localhost 77 &
[3] 25427
[4] 25428
[5] 25429
solaris % result: 4356
result: 3025
result: 5929
На этот раз мы видим, что результаты выводятся одновременно, один за другим. Взглянув на выводимый сервером текст, отметим, что используются три серверных потока и все они выполняются одновременно:
solaris % server
thread 1 started, arg = 55
thread 4 started, arg = 77
thread 6 started, arg = 66
thread 6 done
thread 1 done
thread 4 done
ПРИМЕЧАНИЕ
Одним из печальных следствий изменений, требуемых для реализации многопоточности, является уменьшение количества систем, поддерживающих новый код. Например, в Digital Unix 4.0B и BSD/OS 3.1 используется старая система RPC, не поддерживающая многопоточность. Это означает, что если мы хотим компилировать и использовать нашу программу в системах обоих типов, нам нужно использовать условия #ifdef для обработки различий в вызовах клиента и сервера. Конечно, клиент в BSD/OS, не являющийся многопоточным, может вызвать процедуру многопоточного сервера в Solaris, но если мы хотим, чтобы клиент или сервер компилировался в обоих типах систем, исходный код нужно изменить, предусмотрев различия.