ONC+ 開発ガイド

第 7 章 マルチスレッド RPC プログラミング

このマニュアルには、Solaris でのマルチスレッドプログラミングについては説明していません。次の項目については、『マルチスレッドのプログラミング』を参照してください。

TI-RPC は、マルチスレッド RPC サーバーをサポートします。マルチスレッドサーバーと シングルスレッドのサーバーの違いは、マルチスレッドサーバーがスレッドの技術を使用して複数のクライアント要求を同時に処理することです。マルチスレッドサーバーの方が、高度なパフォーマンスと可用性を備えています。

マルチスレッドクライアントの概要

マルチスレッド対応のクライアントプログラムでは、RPC 要求が出されるたびにスレッドを 1 つ作成することができます。複数スレッドが同一のクライアントハンドルを共有する場合は、RPC 要求を発行できるのは一度に 1 つのスレッドだけです。これに対して、複数スレッドがそれぞれ固有のクライアントハンドルを使用して RPC 要求を出す場合には、複数の要求が同時に処理されます。図 4–1 は、異なるクライアントハンドルを使用するクライアント側の 2 つのスレッドから成るマルチスレッド対応クライアント環境でのタイミングの例を示したものです。

次の図は、クライアント側でマルチスレッド rstat プログラムを実行する場合を示します。クライアントプログラムは各ホストに対してスレッドを作成します。スレッドはそれぞれ、固有のクライアントハンドルを作成し、指定のホストにさまざまな RPC 呼び出しを行なっています。クライアント側の各スレッドは異なるハンドルを使用して RPC 呼び出しを行うため、RPC 呼び出しは同時に実行されます。

図 7–1 異なるクライアントハンドルを使用する 2 つのクライアントスレッド (リアルタイム)

Graphic


注 –

RPC マルチスレッド対応アプリケーションを作成する場合は常に、スレッドライブラリをリンクしなければなりません。スレッドライブラリは、コマンド行で最後にリンクする必要があります。そのためには、コンパイルコマンドで -lthread を指定します。


cc rstat.c -lnsl -lthread のように入力して、プログラムを作成します。

マルチスレッドサーバーの概要

Solaris 2.4 より前のバージョンでは、RPC サーバーはシングルスレッドでした。つまり、クライアント側から要求が来るごとに処理していました。たとえば、2 つの要求を同時に受け取り、最初の処理に 30 秒、次の処理に1 秒かかるとします。2 つめの要求を出したクライアントは最初の処理が完了するまで待たなければなりません。これは、各 CPU が異なる要求を同時に処理するマルチプロセッササーバー環境を利用できず、他の要求がサーバーよって処理することができるのに 1 つの要求の I/O の完了を待っている状態が生じ、望ましいものではありません。

RPC ライブラリでは、サービス開発者がエンドユーザーにより高いパフォーマンスを提供するマルチスレッドサーバーを作成できる機能が提供されます。TI-RPC では、サーバーのマルチスレッドの 2 つのモードがサポートされます。自動マルチスレッドモードとユーザーマルチスレッドモードです。

自動モードでは、サーバーは、クライアント要求を受信するごとに新規スレッドを自動的に作成します。このスレッドは要求を処理し、応答してから終了します。ユーザーモードでは、サービス開発者が、入って来るクライアント要求を同時に処理するスレッドを作成、管理します。自動モードはユーザーモードより使用はしやすいのですが、ユーザーモードの方が特別な要件を必要とするサービス開発者に対して柔軟性があります。


注 –

RPC マルチスレッド対応アプリケーションを作成する場合は常に、スレッドライブラリ内でリンクしなければなりません。スレッドライブラリは、コマンド行で最後にリンクする必要があります。そのためには、コンパイルコマンドで -lthread オプションを指定します。


サーバー側のマルチスレッドをサポートする呼び出しでは、rpc_control()svc_done() がサポートされています。これらの呼び出しによってサーバー側でマルチスレッド処理が行えるようになりました。rpc_control() 呼び出しがマルチスレッドモードを設定するために、自動モードとユーザーモードの両方で使用されます。サーバーが自動モードを使用する場合には、svc_done() を呼び出す必要はありません。ユーザーモードの場合には、サーバーが要求処理からのリソースを再要求できるようにするため、svc_done() は各クライアント要求が処理されてから呼び出されなければなりません。さらにマルチスレッド RPC サーバーは、svc_run() をマルチスレッド対応で呼び出さなければなりません。svc_getreqpoll()svc_getreqset() は、MT アプリケーション対応ではありません。

サーバープログラムが新規インタフェース呼び出しを行わない場合には、デフォルトのモードのシングルスレッドモードのままです。

サーバーが使用しているモードに関係なく、RPC サーバー手続きはマルチスレッド対応にしなければなりません。通常これは、すべての静的変数とグロール変数が mutex ロックで保護される必要がある、ということです。相互排他と他の同期 API は、synch.h で定義されます。さまざまな同期インタフェースのリストは、 condition(3THR)rwlock(3THR)、および mutex(3THR) のマニュアルページを参照してください。

次の図は、マルチスレッドモードのどちらかで実行されるサーバーの実行タイミングを示します。

図 7–2 マルチスレッド RPC サーバーのタイミングダイアグラム

Graphic

サービストランスポートハンドルの共有

サービストランスポートハンドル、SVCXPRT には、引数を復号化するための領域と結果をコード化するための領域である 1 つのデータ領域があります。したがって、デフォルトでは、シングルスレッドモードであり、この構造は、これらの操作を行う関数を呼び出すスレッド間では自由に共有することはできません。ただし、サーバーが、マルチスレッド自動モードまたはユーザーモードにある場合には、この構造のコピーは、同時要求処理を可能にするために、サービスディスパッチ用のプログラムに引き渡されます。これらの状況では、ルーチンのマルチスレッド対応ではない一部のルーチンがマルチスレッド対応となります。特別に注意書きがない場合には、サーバーインタフェースは通常、マルチスレッド対応です。サーバー側のインタフェースについての詳細は、rpc_svc_calls(3NSL) のマニュアルページを参照してください。

自動マルチスレッド対応モード

マルチスレッド自動モードでは、RPC ライブラリはスレッドを作成し、管理することができます。サービス開発者が新規インタフェース呼び出し、rpc_control() を呼び出し、svc_run() を呼び出す前にサーバーをマルチスレッド自動モードにします。このモードでは、プログラマはサービス手続きがマルチスレッド対応であることを確認するだけで十分です。

rpc_control() の使用によって、アプリケーションでグローバル RPC 属性を設定できます。現在はサービス側の操作しかサポートしていません。次の表は、自動モード用に定義された rpc_control() 操作を示します。詳細は rpc_control(3NSL) マニュアルページを参照してください。

表 7–1 rpc_control ライブラリルーチン

ルーチン 

説明 

RPC_SVC_MTMODE_SET()

マルチスレッドモードの設定 

RPC_SVC_MTMODE_GET()

マルチスレッドの取得 

RPC_SVC_THRMAX_SET()

最大スレッド数の設定 

RPC_SVC_THRMAX_GET()

最大スレッド数の取得 

RPC_SVC_THRTOTAL_GET ()

現在アクティブなスレッドの合計数  

RPC_SVC_THRCREATES_GET ()

RPC ライブラリ作成のスレッドの累積数 

RPC_SVC_THRERRORS_GET ()

RPC ライブラリ内の thr_create() エラー数


注 –

表 7–1 の get 演算は、RPC_SVC_MTMODE_GET() 以外はすべて、自動マルチスレッドモードにだけ適用されます。マルチスレッドユーザーモードまたはデフォルトのシングルスレッドモードで使用すると、演算の結果が定義されません。


デフォルトでは、RPC ライブラリが一度に作成できるスレッドの最大数は 16 です。サーバーが 16 以上のクライアント要求を同時に処理する必要がある場合には、スレッドの最大数を指定して設定する必要があります。このパラメータは、サーバーによっていつでも設定できます。これによって、サーバー開発者はサーバーによって使用されるスレッドリソースの上限を設定できます。例 7–1 は、マルチスレッド自動モードに作成された RPC プログラムの例です。この例では、スレッドの最大数は 20 に設定されています。

マルチスレッドのパフォーマンスは、関数 svc_getargs() が、NULLPROCS 以外の手続きによって呼び出されるごとに、引数 (この場合には xdr_void())がない場合でも改善されていきます。これは、マルチスレッド自動モードとマルチスレッドユーザーモードの両方においてです。詳細は、rpc_svc_calls(3NSL) のマニュアルページを参照してください。


注 –

RPC マルチスレッド対応アプリケーションを作成する場合は常に、スレッドライブラリ内でリンクしなければなりません。スレッドライブラリは、コマンド行で最後にリンクする必要があります。そのためには、コンパイルコマンドで -lthread オプションを指定します。


次の例は、マルチスレッド自動モードでのサーバーを示したものです。このプログラムをコンパイルするには、cc time_svc.c -lnsl -lthread を実行します。


例 7–1 マルチスレッド自動モードのサーバー

		#include <stdio.h>
		#include <rpc/rpc.h>
 	#include <synch.h>
		#include <thread.h>
 	#include "time_prot.h"
 
		void time_prog();
 
	 main(argc, argv)
		int argc;
		char *argv[];
 	{
		int transpnum;
		char *nettype;
 	int mode = RPC_SVC_MT_AUTO;
		int max = 20;      /* スレッド最大数を 20 に設定 */
 
	 if (argc> 2) {
	 	fprintf(stderr, "usage: %s [nettype]\n", argv[0]);
			exit(1);
 	}
 
		if (argc == 2)
			nettype = argv[1];
		else
			nettype = "netpath";
 
	 if (!rpc_control(RPC_SVC_MTMODE_SET, &mode)) {
			printf("RPC_SVC_MTMODE_SET: failed\n");
			exit(1);
		}
 	if (!rpc_control(RPC_SVC_THRMAX_SET, &max)) {
			printf("RPC_SVC_THRMAX_SET: failed\n");
			exit(1);
		}
 	transpnum = svc_create( time_prog, TIME_PROG, TIME_VERS,
 			nettype);
 
		if (transpnum == 0) {
 		fprintf(stderr, "%s: cannot create %s service.\n",
			argv[0], nettype);	
			exit(1);
		}
	 svc_run();
	}
 
	/*
	 * サーバーのディスパッチプログラムです。RPC サーバーライブラリは、
	 * サーバーのディスパッチャルーチン time_prog () を実行するスレッドを
	 * 作成します。RPC ライブラリがスレッドを廃棄した後に行われます。
  */
 
	static void
	time_prog(rqstp, transp)
		struct svc_req *rqstp;
		SVCXPRT *transp;
 {
 
		switch (rqstp->rq_proc) {
 		case NULLPROC:
				svc_sendreply(transp, xdr_void, NULL);
 			return;
			case TIME_GET:
				dotime(transp);
 			break;
			default:
				svcerr_noproc(transp);
 			return;
		}
	}
	dotime(transp)
 SVCXPRT *transp;
	{
	
		struct timev rslt;
		time_t thetime;
	
 	thetime = time((time_t *)0);
		rslt.second = thetime % 60;
 	thetime /= 60;
		rslt.minute = thetime % 60;
 	thetime /= 60;
		rslt.hour = thetime % 24;
 	if (!svc_sendreply(transp, xdr_timev,(caddr_t) &rslt)) {
 		svcerr_systemerr(transp);
		}
	}

次に、サーバーの time_prot.h ヘッダーファイルを示します。


例 7–2 マルチスレッド自動モード : time_prot.h ヘッダーファイル

#include <rpc/types.h>

 
		struct timev {
			int second;

			int minute;
			int hour;
		};

 
		typedef struct timev timev;

		bool_t xdr_timev();
 
		#define TIME_PROG 0x40000001

		#define TIME_VERS 1
		#define TIME_GET 1

マルチスレッドユーザーモード

マルチスレッドユーザーモードでは、RPC ライブラリはスレッドを作成しません。このモードは、基本的には、シングルスレッド、またはデフォルトのモードのように作動します。唯一の違いは、データ構造のコピー (サービスディスパッチルーチンへのトランスポートサービスなど) をマルチスレッド対応に引き渡す点です。

RPC サーバー開発者は、スレッドライブラリ全体のスレッドの作成と管理に対する責任を持ちます。ディスパッチルーチンでは、サービス開発者は、手続きの実行を新規作成のまたは既存のスレッドに割り当てることができます。thr_create() API は、さまざまな属性を持つスレッドを作成するために使用されます。すべてのスレッドのライブラリインタフェースは、thread.h で定義されます。詳細は、pthread_create(3THR) のマニュアルページを参照してください。

このモードは、サーバー開発者に幅広い柔軟性を提供しています。スレッドは、サービス要件に応じたスタックサイズを持ちます。スレッドは限定されます。異なる手続きは、異なる特長を持つスレッドによって実行されます。サービス開発者は、サービスの一部をシングルスレッドで実行できます。また、特定のスレッドに固有のシグナル処理を行うこともできます。

自動モードの場合と同じように、rpc_control() ライブラリは、ユーザーモードに切り換える場合に使用されます。 表 7–1 に示した rpc_control() 演算 (RPC_SVC_MTMODE_GET() 以外) は、マルチスレッド自動モードにだけ適用されます。マルチスレッドユーザーモードまたはシングルスレッドのデフォルトモードで使用すると、演算の結果が定義できません。

ユーザーモードでのライブラリリソースの解放

マルチスレッドユーザーモードでは、サービス手続きは、戻しの前に svc_done() を呼び出さなければなりません。svc_done() は、クライアント要求が指定のサービストランスポートハンドルに向けたサービスに割り当てたリソースを解放しなければなりません。この機能は、クライアント要求がサービスされた後、あるいは応答の送信を妨げたエラーまたは異常な状態の後に呼び出されます。svc_done() が呼び出された後に、サービストランスポートハンドルは、サービス手続きによって参照されるべきではありません。次の例は、マルチスレッドユーザーモードでのサーバーを示します。


注 –

svc_done() は、マルチスレッドユーザーモード内でだけ呼び出すことができます。詳細は、rpc_svc_calls(3NSL) のマニュアルページを参照してください。



例 7–3 マルチスレッドユーザーモード : rpc_test.h

	#define	SVC2_PROG 0x30000002
	#define	SVC2_VERS 1
	#define SVC2_PROC_ADD 1)
	#define SVC2_PROC_MULT 2
 
	struct intpair {
		u_short a;
		u_short b;
	};
 
	typedef struct intpair intpair;
 
	struct svc2_add_args {
		int argument;
		SVCXPRT *transp;
 };
 
	struct svc2_mult_args {
	 intpair mult_argument;
		SVCXPRT *transp;
	};
 
	extern bool_t xdr_intpair();
 
	#define NTHREADS_CONST 500

次の例は、マルチスレッドユーザーモードでのクライアントです。


例 7–4 マルチスレッドユーザーモードでのクライアント

#define	 _REENTRANT
#include <stdio.h>

#include <rpc/rpc.h>
#include <sys/uio.h>

#include <netconfig.h>
#include <netdb.h>

#include <rpc/nettype.h>
#include <thread.h>
#include "rpc_test.h"
void *doclient();
int NTHREADS;
struct thread_info {
	thread_t client_id;
	int client_status;
};
struct thread_info save_thread[NTHREADS_CONST];
main(argc, argv)
	int argc;
	char *argv[];
{
	int index, ret;
	int thread_status;
	thread_t departedid, client_id;
	char *hosts;
	if (argc < 3) {
		printf("Usage: do_operation [n] host\n");
		printf("\twhere n is the number of threads\n");
		exit(1);
	} else
		if (argc == 3) {
			NTHREADS = NTHREADS_CONST;
			hosts = argv[1];  /* live_host */
		} else {
			NTHREADS = atoi(argv[1]);
			hosts = argv[2];
		}
	for (index = 0; index < NTHREADS; index++){
		if (ret = thr_create(NULL, NULL, doclient,
		(void *)  hosts, THR_BOUND, &client_id)){
			printf("thr_create failed: return value %d", ret);
			printf(" for %dth thread\n", index);
			exit(1);
		}
		save_thread[index].client_id = client_id;
	}
	for (index = 0; index < NTHREADS; index++){

		if (thr_join(save_thread[index].client_id, &departedid,	
		(void *)
		&thread_status)){
			printf("thr_join failed for thread %d\n",
			save_thread[index].client_id);
			exit(1);
		}
		save_thread[index].client_status = thread_status;
	}
}
	void *doclient(host)
	char *host;
{
	struct timeval tout;
	enum clnt_stat test;
	int result = 0;
	u_short mult_result = 0;
	int add_arg;
	int EXP_RSLT;
	intpair pair;
	CLIENT *clnt;
	if ((clnt = clnt_create(host, SVC2_PROG, SVC2_VERS, "udp"
==NULL) {
		clnt_pcreateerror("clnt_create error: ");
		thr_exit((void *) -1);
	}
	tout.tv_sec = 25;
	tout.tv_usec = 0;
	memset((char *) &result, 0, sizeof (result));

	memset((char *) &mult_result, 0, sizeof (mult_result));
	if (thr_self() % 2){
		EXP_RSLT = thr_self() + 1;
		add_arg = thr_self();
		test = clnt_call(clnt, SVC2_PROC_ADD, (xdrproc_t) xdr_int,

		(caddr_t) &add_arg, (xdrproc_t) xdr_int, (caddr_t) &result,
		tout);
	} else {
		pair.a = (u_short) thr_self();
		pair.b = (u_short) 1;
		EXP_RSLT = pair.a * pair.b;
		test = clnt_call(clnt, SVC2_PROC_MULT, (xdrproc_t)
		xdr_intpair,
		(caddr_t) &pair, (xdrproc_t) xdr_u_short,

		(caddr_t) &mult_result, tout);
		result = mult_result;
	}
	if (test != RPC_SUCCESS) {
		printf("THREAD: %d clnt_call hav
		thr_exit((void *) -1);
	};
	thr_exit((void *) 0);
}

引数がない場合であっても、関数 svc_getargs()NULLPROC 以外の各手続きに呼び出されるようにすれば、マルチスレッドのパフォーマンスが改善されます。この例では、xdr_void を使用できます。これは、マルチスレッド自動モードとマルチスレッドユーザーモードのどちらにも当てはまります。詳細は、rpc_svc_calls(3NSL) のマニュアルページを参照してください。


注 –

RPC マルチスレッド対応アプリケーションを作成する場合は常に、スレッドライブラリ内でリンクしなければなりません。スレッドライブラリは、コマンド行で最後にリンクする必要があります。コンパイルコマンドに -lthread オプションを指定します。