使用中の開発プラットフォームでスレッドがサポートされない場合、この章は無視してください。この章の項目は、次のとおりです。
注意: Pro*C/C++プリコンパイラとXAを併用する場合は、XAのマルチスレッドを使用する必要があります。EXEC SQL ENABLE THREADS文を使用してPro*C/C++のマルチスレッドを使用するとエラーになります。 |
マルチスレッド・アプリケーションでは、共有のアドレス空間で複数のスレッドが実行されます。スレッドはプロセス内で実行される軽量のサブプロセスです。コードとデータ・セグメントは共有しますが、独自のプログラム・カウンタ、マシン・レジスタおよびスタックがあります。グローバル変数と静的変数はすべてのスレッドに共通であり、通常、アプリケーション内の複数のスレッドからこれらの変数へのアクセスを管理するには、相互排他メカニズムが必要です。mutexは、データの整合性が保たれることを保証する同期化メカニズムです。
mutexの詳細は、マルチスレッドに関するテキストを参照してください。マルチスレッド・アプリケーションの詳細は、スレッド機能に関するマニュアルを参照してください。
Pro*C/C++プリコンパイラは、マルチスレッドのOracleサーバー・アプリケーションの開発を(マルチスレッド・アプリケーションをサポートするプラットフォームで)サポートします。サポートされている機能は次のとおりです。
スレッド・セーフなコードを生成するコマンドライン・オプション
マルチスレッド処理をサポートする埋込みSQL文およびディレクティブ
スレッド・セーフなSQLLIBと、その他のクライアント側Oracleライブラリ
注意: プラットフォームが特定のスレッド・パッケージをサポートしている場合も、プラットフォーム固有のOracleマニュアルを参照して、Oracleでそのスレッド・パッケージがサポートされているかどうかを判断してください。 |
次の各項目では、前述の機能を使用してマルチスレッドのPro*C/C++アプリケーションを開発する方法を説明します。
マルチスレッド・アプリケーションのランタイム・コンテキスト
ランタイム・コンテキストを使用する2つのモデル
マルチスレッド・アプリケーションのユーザー・インタフェース
Pro*C/C++を使用してマルチスレッド・アプリケーションを作成する場合のプログラミング上の考慮事項
マルチスレッドのPro*C/C++アプリケーションの例
Pro*C/C++には、スレッドと接続を疎結合するために、ランタイム・コンテキストという概念が導入されています。ランタイム・コンテキストには、次のリソースと現在の状態が含まれています。
1つ以上のOracleサーバーへの0個以上の接続
サーバーへの接続に使用される0個以上のカーソル
MODE、HOLD_CURSOR、RELEASE_CURSOR、SELECT_ERRORなどのインライン・オプション
Pro*C/C++プリコンパイラを使用すると、スレッドと接続を疎結合せずに、スレッドをランタイム・コンテキストに疎結合できます。また、Pro*C/C++では、アプリケーションでランタイム・コンテキストのハンドルを定義して、そのハンドルをあるスレッドから別のスレッドに渡すことができます。
たとえば、対話形式のアプリケーションで、スレッドT1を作成し、問合せを実行して先頭の10行をアプリケーションに戻します。その後、T1は終了します。必要なユーザー入力が取得されると、別のスレッドT2が作成され(または既存のスレッドが使用され)、T1のランタイム・コンテキストがT2に渡されます。T2は同じカーソルを処理して次の10行をフェッチできます。図11-1「接続とスレッドの疎結合」を参照してください。
マルチスレッドのPro*C/C++アプリケーションでランタイム・コンテキストを使用した2つの可能なモデルを次に示します。
単一のランタイム・コンテキストを共有する複数のスレッド
複数のランタイム・コンテキストを使用する複数のスレッド
使用するランタイム・コンテキストのモデルに関係なく、1つのランタイム・コンテキストを複数のスレッドで同時に共有することはできません。複数のスレッドで同じランタイム・コンテキストを同時に使用すると、ランタイム・エラーが発生します。
図11-2は、マルチスレッド環境で実行されるアプリケーションを示しています。複数のスレッドが単一のランタイム・コンテキストを共有して1つまたは複数のSQL文を処理します。なお、ランタイム・コンテキストを同時に複数のスレッドで共有することはできません。図11-2のmutexは、同時使用を防ぐ方法を示しています。
図11-3に、複数のランタイム・コンテキストを使用して複数スレッドを実行するアプリケーションを示します。この場合、各スレッドに専用のランタイム・コンテキストがあるため、アプリケーションにmutexは必要ありません。
Pro*C/C++プリコンパイラは、次に示すユーザー・インタフェース機能によってマルチスレッド・アプリケーションをサポートしています。
コマンドライン・オプションTHREADS=YES|NO
埋込みSQL文とディレクティブ
スレッド・セーフなSQLLIBパブリック関数
THREADS=YESをコマンドラインに指定すると、ガイドラインに従っている場合、生成されたコードがスレッド・セーフであることがPro*C/C++プリコンパイラにより保証されます。THREADS=YESと指定すると、Pro*C/C++ではすべてのSQL文がユーザー定義のランタイム・コンテキスト範囲内で実行されることが検証されます。プログラムがこの要件を満たさない場合は、プリコンパイラ・エラーが戻されます。
次の埋込みSQL文およびディレクティブは、ランタイム・コンテキストおよびスレッドの定義と使用をサポートしています。
EXEC SQL ENABLE THREADS;
EXEC SQL CONTEXT ALLOCATE :context_var;
EXEC SQL CONTEXT USE { :context_var | DEFAULT};
EXEC SQL CONTEXT FREE :context_var;
これらのEXEC SQL文では、context_varがランタイム・コンテキストへのハンドルで、次のようにsql_context型として宣言する必要があります。
sql_context <context_variable>;
DEFAULTを使用すると、別のCONTEXT USE文で上書きされるまで、字句的に続くすべての埋込みSQL文でデフォルト(グローバル)・ランタイム・コンテキストが使用されます。
この実行SQL文は、複数のスレッドをサポートするプロセスを初期化します。このSQL文は、マルチスレッド・アプリケーション内の最初の実行SQL文にしてください。
注意: Pro*C/C++プリコンパイラとXAを併用する場合は、XAのマルチスレッドを使用する必要があります。EXEC SQL ENABLE THREADS文を使用してPro*Cのマルチスレッドを使用するとエラーになります。 |
この実行SQL文は指定されたランタイム・コンテキストにメモリーを割り当てて初期化します。ランタイム・コンテキスト変数はsql_context型として宣言する必要があります。
このディレクティブはプリコンパイラに、後続の実行SQL文に対して指定したランタイム・コンテキストを使用するように指示します。指定するランタイム・コンテキストを、EXEC SQL CONTEXT ALLOCATE文を使用して事前に割り当てる必要があります。
EXEC SQL CONTEXT USEディレクティブは、EXEC SQL WHENEVERディレクティブと同様に、指定したソース・ファイル内でこのディレクティブの後に続くすべての実行SQL文に影響し、C言語の標準のスコープ規則には従いません。次の例では、function2()
のUPDATE文はグローバル・ランタイム・コンテキストctx1を使用しています。
sql_context ctx1; /* declare global context ctx1 */ function1() { sql_context :ctx1; /* declare local context ctx1 */ EXEC SQL CONTEXT ALLOCATE :ctx1; EXEC SQL CONTEXT USE :ctx1; EXEC SQL INSERT INTO ... /* local ctx1 used for this stmt */ ... } function2() { EXEC SQL UPDATE ... /* global ctx1 used for this stmt */ }
ローカル・コンテキストを使用した後でグローバル・コンテキストを使用するには、次のコードをfunction1()に追加します。
function1() { sql_context :ctx1; /* declare local context ctx1 */ EXEC SQL CONTEXT ALLOCATE :ctx1; EXEC SQL CONTEXT USE :ctx1; EXEC SQL INSERT INTO ... /* local ctx1 used for this stmt */ EXEC SQL CONTEXT USE DEFAULT; EXEC SQL INSERT INTO ... /* global ctx1 used for this stmt */ ... }
次の例に、グローバル・ランタイム・コンテキストはありません。プリコンパイラでは、UPDATE文に対して生成されたコードでctx1ランタイム・コンテキストが参照されます。ただし、function2()
のスコープにコンテキスト変数がないため、コンパイル時にエラーが発生します。
function1() { sql_context ctx1; /* local context variable declared */ EXEC SQL CONTEXT ALLOCATE :ctx1; EXEC SQL CONTEXT USE :ctx1; EXEC SQL INSERT INTO ... /* ctx1 used for this statement */ ... } function2() { EXEC SQL UPDATE ... /* Error! No context variable in scope */ }
次に示すコード例では、2つの典型的なプログラミング・モデルに埋込みSQL文とプリコンパイラ・ディレクティブを使用する方法を示しています。thread_create()を使用してスレッドを作成します。
最初の例では、複数のスレッドが複数のランタイム・コンテキストを使用する場合を示します。
main() { sql_context ctx1,ctx2; /* declare runtime contexts */ EXEC SQL ENABLE THREADS; EXEC SQL CONTEXT ALLOCATE :ctx1; EXEC SQL CONTEXT ALLOCATE :ctx2; ... /* spawn thread, execute function1 (in the thread) passing ctx1 */ thread_create(..., function1, ctx1); /* spawn thread, execute function2 (in the thread) passing ctx2 */ thread_create(..., function2, ctx2); ... EXEC SQL CONTEXT FREE :ctx1; EXEC SQL CONTEXT FREE :ctx2; ... } void function1(sql_context ctx) { EXEC SQL CONTEXT USE :ctx; /* execute executable SQL statements on runtime context ctx1!!! */ ... } void function2(sql_context ctx) { EXEC SQL CONTEXT USE :ctx; /* execute executable SQL statements on runtime context ctx2!!! */ ... }
次の例では、共通のランタイム・コンテキストを共有する複数のスレッドを使用する方法を示します。function1()
およびfunction2()
で実行されるSQL文は同時に実行される可能性があるため、すべての実行EXEC SQL文をmutexで囲むことで、データ操作を逐次的、つまり安全に行うことが必要です。
main() { sql_context ctx; /* declare runtime context */ EXEC SQL CONTEXT ALLOCATE :ctx; ... /* spawn thread, execute function1 (in the thread) passing ctx */ thread_create(..., function1, ctx); /* spawn thread, execute function2 (in the thread) passing ctx */ thread_create(..., function2, ctx); ... } void function1(sql_context ctx) { EXEC SQL CONTEXT USE :ctx; /* Execute SQL statements on runtime context ctx. */ ... } void function2(sql_context ctx) { EXEC SQL CONTEXT USE :ctx; /* Execute SQL statements on runtime context ctx. */ ... }
OracleはSQLLIBコードがスレッド・セーフであることを保証しますが、Pro*C/C++のソース・コードがスレッドで正しく動作するように設計する必要があります。たとえば、静的変数とグローバル変数は慎重に使用してください。
また、マルチスレッド・アプリケーションの設計時には次の点に注意してください。
SQLCAをスレッド・セーフな構造体として宣言します。通常は自動変数として、ランタイム・コンテキストごとに1つずつ宣言します。
SQLDAをスレッド・セーフな構造体として宣言します(SQLCAと同様)。通常は、自動変数としてランタイム・コンテキストごとに1つずつ宣言します。
ホスト変数をスレッド・セーフになるように宣言します。つまり、静的ホスト変数およびグローバルなホスト変数の使用方法を慎重に検討します。
複数のスレッドでランタイム・コンテキストを同時に使用するのを避けます。
デフォルトのデータベース接続を使用するか、あるいはAT句を使用して明示的に定義するかを判断します。
さらに、複数の埋込みSQLの実行文(EXEC SQL UPDATEなど)をランタイム・コンテキストで同時に未解決にしないでください。
プリコンパイルしたアプリケーションに対する既存の要件も適用されます。たとえば、ある特定のカーソルへの参照はすべて同じソース・ファイル内で指定する必要があります。
次のプログラムは、マルチスレッドの埋込みSQLアプリケーションを作成する1つの方法です。このプログラムでは、スレッドと同じ数のセッションが作成されます。それぞれのスレッドは0個以上のトラングサションを実行します。これはレコードと呼ばれる一時的な構造体によって指定されます。
注意: このプログラムはSolarisが動作しているSun社のワークステーション専用に開発されています。このプログラムでは、DCEまたはSolarisのスレッド・パッケージを使用できます。スレッド・パッケージの可用性は、プラットフォーム固有のマニュアルを参照してください。 |
/* * Name: Thread_example1.pc * * Description: This program illustrates how to use threading in * conjunction with precompilers. The program creates as many * sessions as there are threads. Each thread executes zero or * more transactions, that are specified in a transient * structure called 'records'. * Requirements: * The program requires a table 'ACCOUNTS' to be in the schema * scott/tiger. The description of ACCOUNTS is: * SQL> desc accounts * Name Null? Type * ------------------------------- ------- ------ * ACCOUNT NUMBER(36) * BALANCE NUMBER(36,2) * * For proper execution, the table should be filled with the accounts * 10001 to 10008. * * */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sqlca.h> #define _EXC_OS_ _EXC__UNIX #define _CMA_OS_ _CMA__UNIX #ifdef DCE_THREADS #include <pthread.h> #else #include <thread.h> #endif /* Function prototypes */ void err_report(); #ifdef DCE_THREADS void do_transaction(); #else void *do_transaction(); #endif void get_transaction(); void logon(); void logoff(); #define CONNINFO "scott/tiger" #define THREADS 3 struct parameters { sql_context * ctx; int thread_id; }; typedef struct parameters parameters; struct record_log { char action; unsigned int from_account; unsigned int to_account; float amount; }; typedef struct record_log record_log; record_log records[]= { { 'M', 10001, 10002, 12.50 }, { 'M', 10001, 10003, 25.00 }, { 'M', 10001, 10003, 123.00 }, { 'M', 10001, 10003, 125.00 }, { 'M', 10002, 10006, 12.23 }, { 'M', 10007, 10008, 225.23 }, { 'M', 10002, 10008, 0.70 }, { 'M', 10001, 10003, 11.30 }, { 'M', 10003, 10002, 47.50 }, { 'M', 10002, 10006, 125.00 }, { 'M', 10007, 10008, 225.00 }, { 'M', 10002, 10008, 0.70 }, { 'M', 10001, 10003, 11.00 }, { 'M', 10003, 10002, 47.50 }, { 'M', 10002, 10006, 125.00 }, { 'M', 10007, 10008, 225.00 }, { 'M', 10002, 10008, 0.70 }, { 'M', 10001, 10003, 11.00 }, { 'M', 10003, 10002, 47.50 }, { 'M', 10008, 10001, 1034.54}}; static unsigned int trx_nr=0; #ifdef DCE_THREADS pthread_mutex_t mutex; #else mutex_t mutex; #endif /********************************************************************* * Main ********************************************************************/ main() { sql_context ctx[THREADS]; #ifdef DCE_THREADS pthread_t thread_id[THREADS]; pthread_addr_t status; #else thread_t thread_id[THREADS]; int status; #endif parameters params[THREADS]; int i; EXEC SQL ENABLE THREADS; EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); /* Create THREADS sessions by connecting THREADS times */ for(i=0;i<THREADS;i++) { printf("Start Session %d....",i); EXEC SQL CONTEXT ALLOCATE :ctx[i]; logon(ctx[i],CONNINFO); } /*Create mutex for transaction retrieval */ #ifdef DCE_THREADS if (pthread_mutex_init(&mutex,pthread_mutexattr_default)) #else if (mutex_init(&mutex, USYNC_THREAD, NULL)) #endif { printf("Can't initialize mutex\n"); exit(1); } /*Spawn threads*/ for(i=0;i<THREADS;i++) { params[i].ctx=ctx[i]; params[i].thread_id=i; printf("Thread %d... ",i); #ifdef DCE_THREADS if (pthread_create(&thread_id[i],pthread_attr_default, (pthread_startroutine_t)do_transaction, (pthread_addr_t) ¶ms[i])) #else if (status = thr_create (NULL, 0, do_transaction, ¶ms[i], 0, &thread_id[i])) #endif printf("Cant create thread %d\n",i); else printf("Created\n"); } /* Logoff sessions....*/ for(i=0;i<THREADS;i++) { /*wait for thread to end */ printf("Thread %d ....",i); #ifdef DCE_THREADS if (pthread_join(thread_id[i],&status)) printf("Error when waiting for thread % to terminate\n", i); else printf("stopped\n"); printf("Detach thread..."); if (pthread_detach(&thread_id[i])) printf("Error detaching thread! \n"); else printf("Detached!\n"); #else if (thr_join(thread_id[i], NULL, NULL)) printf("Error waiting for thread to terminate\n"); #endif printf("Stop Session %d....",i); logoff(ctx[i]); EXEC SQL CONTEXT FREE :ctx[i]; } /*Destroys mutex*/ #ifdef DCE_THREADS if (pthread_mutex_destroy(&mutex)) #else if (mutex_destroy(&mutex)) #endif { printf("Can't destroy mutex\n"); exit(1); } } /********************************************************************* * Function: do_transaction * * Description: This functions executes one transaction out of the * records array. The records array is 'managed' by * the get_transaction function. * * ********************************************************************/ #ifdef DCE_THREADS void do_transaction(params) #else void *do_transaction(params) #endif parameters *params; { struct sqlca sqlca; record_log *trx; sql_context ctx=params->ctx; /* Done all transactions ? */ while (trx_nr < (sizeof(records)/sizeof(record_log))) { get_transaction(&trx); EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); EXEC SQL CONTEXT USE :ctx; printf("Thread %d executing transaction\n",params->thread_id); switch(trx->action) { case 'M': EXEC SQL UPDATE ACCOUNTS SET BALANCE=BALANCE+:trx->amount WHERE ACCOUNT=:trx->to_account; EXEC SQL UPDATE ACCOUNTS SET BALANCE=BALANCE-:trx->amount WHERE ACCOUNT=:trx->from_account; break; default: break; } EXEC SQL COMMIT; } } /***************************************************************** * Function: err_report * * Description: This routine prints out the most recent error * ****************************************************************/ void err_report(sqlca) struct sqlca sqlca; { if (sqlca.sqlcode < 0) printf("\n%.*s\n\n",sqlca.sqlerrm.sqlerrml,sqlca.sqlerrm.sqlerrmc); exit(1); } /***************************************************************** * Function: logon * * Description: Logs on to the database as USERNAME/PASSWORD * *****************************************************************/ void logon(ctx,connect_info) sql_context ctx; char * connect_info; { EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); EXEC SQL CONTEXT USE :ctx; EXEC SQL CONNECT :connect_info; printf("Connected!\n"); } /****************************************************************** * Function: logoff * * Description: This routine logs off the database * ******************************************************************/ void logoff(ctx) sql_context ctx; { EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); EXEC SQL CONTEXT USE :ctx; EXEC SQL COMMIT WORK RELEASE; printf("Logged off!\n"); } /****************************************************************** * Function: get_transaction * * Description: This routine returns the next transaction to process * ******************************************************************/ void get_transaction(trx) record_log ** trx; { #ifdef DCE_THREADS if (pthread_mutex_lock(&mutex)) #else if (mutex_lock(&mutex)) #endif printf("Can't lock mutex\n"); *trx=&records[trx_nr]; trx_nr++; #ifdef DCE_THREADS if (pthread_mutex_unlock(&mutex)) #else if (mutex_unlock(&mutex)) #endif printf("Can't unlock mutex\n"); }
接続プールとは、いくつかの接続間で再使用できる、データベースへの物理接続グループです。接続プーリング機能の目的は、それぞれの接続に専用接続を使用させないことでパフォーマンスを改善し、リソースの使用を削減することです。
図11-4は、接続プーリングの機能を示します。この例では、アプリケーションの4つのスレッドが接続プールを使用してデータベースと対話しています。この接続プールには物理接続が2つあります。異なるランタイム・コンテキストを使用する4つのスレッドにより、接続プール・ハンドルが使用されます。
thread1() { EXEC SQL CONTEXT ALLOCATE :ctx1; EXEC SQL CONTEXT USE:ctxl; EXEC SQL CONNECT :uid AT :TC1 USING :db_string; ... } thread2() { EXEC SQL CONTEXT ALLOCATE :ctx2; EXEC SQL CONNECT :uid AT :TC2 USING :db_string; ... } thread3() { EXEC SQL CONTEXT ALLOCATE :ctx3; EXEC SQL CONNECT :uid AT :TC3 USING :db_string; EXEC SQL AT :TC3 SELECT count(*) into :count FROM emp; ... } thread4() { EXEC SQL CONTEXT ALLOCATE :ctx4; EXEC SQL CONNECT :uid AT :TC4 USING :db_string; ... }
この例で、TC1、TC2、TC3およびTC4という4つの名前付き接続は、それぞれT1、T2、T3およびT4というスレッドにより作成された仮想接続です。異なるランタイム・コンテキストからの名前付き接続TC1、TC2、TC3およびTC4が1つの接続プールを共有し、接続プール内で使用可能な物理データベース接続を共有します。C1およびC2という2つの物理接続が、4つの名前付き接続にサービスを提供し、同じ1つのデータベースに接続します。
スレッドT1から最初の接続要求TC1を受け取ると、SQLLIBはデータベースへの物理接続C1を1つ含む接続プールを作成します。スレッドT2から別の接続要求TC2が同一データベースに送信されると、C1が空いている場合はデータベースへのTC2要求に対してC1がサービスを提供します。C1が空いていない場合は、要求にサービスを提供するために新しい物理接続C2が作成されます。スレッドT3からTC3という別の接続要求が受信された場合、物理接続C1とC2がどちらも使用中の場合は、TC3は指定された時間待機するか、エラー・メッセージを返します。
スレッドT2が名前付き接続TC2を使用してデータを選択する必要があるときは、物理接続C1またはC2の空いている方を取得します。要求に対してサービスが提供された後、選択されていた接続は接続プール内で再び使用可能になるため、別の名前付き接続または仮想接続がその物理接続を利用できます。
この項のトピックは、次のとおりです。
アプリケーションのプリコンパイル中に接続プーリングを使用可能にするには、CPOOL=YES
コマンドライン・オプションを設定する必要があります。CPOOL=YES/NO
の設定に基づいて、接続プーリング機能が使用可能または使用禁止になります。
注意:
|
表11-1に、接続プーリングのコマンドライン・オプションの一覧を示します。
表11-1 接続プーリングのコマンドライン・オプション
オプション | 有効値 | デフォルト値 | コメント |
---|---|---|---|
CPOOL |
YESまたはNO |
NO |
このオプションに基づいて、プリコンパイラは、SQLLIBに対して接続プーリング機能を使用可能または使用禁止にするように指示する適切なコードを生成します。 注意: このオプションがNOに設定されている場合、プリコンパイラはその他の接続プーリング・オプションを無視します。 |
CMAX |
有効値は1〜65535です。 |
100 |
データベースに対してオープン可能な最大物理接続数を指定します。CMAX値は、CMIN+CINCRより大きく設定する必要があります。 注意: この値に達すると、物理接続をそれ以上オープンできません。 通常のアプリケーションでは、同時データベース操作を100個実行するように設定すれば十分です。ユーザーが適切な値を設定できます。 |
CMIN |
有効値は1〜(CMAX-CINCR)です。 |
2 |
接続プール内の最小物理接続数を指定します。最初は、CMINにより指定された物理接続数がすべてサーバーに対してオープンされます。その後、必要な場合にのみ物理接続がオープンされます。パフォーマンスを最適にするには、CMINを、アプリケーションによる実行が計画または想定される同時実行文の合計数に設定する必要があります。デフォルト値は2です。 |
CINCR |
有効値は1〜(CMAX-CMIN)です。 |
1 |
現在の物理接続数がCMAX値より少ない場合、データベースに対してオープンされる物理接続数の次の増分をアプリケーションで設定できるようにします。不要な数の接続が作成されないように、デフォルト値は1に設定されています。 |
CTIMEOUT |
有効値は1〜65535です。 |
0(未設定)。つまり、タイムアウトになりません。 |
指定された期間(秒単位)より長い間アイドル状態になっている物理接続を終了し、オープンされている物理接続数を最適に保ちます。この属性を設定しない場合、物理接続はタイムアウトしません。このため、接続プールが終了するまで物理接続はクローズしません。 注意: 物理接続を新しく作成すると、サーバーへのラウンドトリップが必要になります。 |
CNOWAIT |
有効値は1〜65535です。 |
0(未設定)。つまり、接続が空くのを待機します。 |
この属性は、プール内の物理接続がすべて使用中で、物理接続の合計数がすでに最大値に達している場合に、アプリケーションで繰り返し物理接続を要求する必要があるかどうかを決定します。物理接続が使用可能でなく、これ以上物理接続をオープンできない場合、この属性が設定されているとエラーが発生します。設定されていない場合、別の接続が取得されるまでコールは待機します。CNOWAITはデフォルトでは設定されないため、スレッドはエラーを返すのではなく、空いている接続を取得できるまで待機します。 |
通常のマルチスレッド・アプリケーションでは、n個の物理接続を持つプールを作成します。nの値は、プリコンパイル中にCMIN値で指定する必要があります。最初の接続コールでは、データベースへの最小数(CMIN)の物理接続が作成されます。新しい要求に対しては、仮想接続(名前付き接続)から物理接続へのマッピングが実行されます。これを次の項で説明します。
ケース1: 物理接続が(すでにオープンされている接続の中で)使用可能な場合、新しい要求にはこの接続がサービスを提供します。
ケース2: 物理接続がすべて使用中の場合
ケース2a: オープンされている接続の数が最大数制限(CMAX)に達していない場合は、CINCR分の新しい接続が作成され、その中の1つが要求の処理に使用されます。
ケース2b: オープンされている物理接続数が最大数(CMAX)に達していて、CNOWAITが設定されていない場合、要求は接続を取得できるまで待機します。CNOWAITが設定されている場合は、「ORA-24401: これ以上の接続は開けません」というエラー・メッセージが表示されます。
次の例の説明は、図11-4を参照してください。
Let CMIN be 1, CMAX be 2, and CINCR be 1.
次の場合を考えます。最初の要求TC1を受け取ると、SQLLIBは物理接続C1を1つ含む接続プールを作成します。別の要求TC2を受け取ると、アプリケーションはC1が空いているかどうかをチェックします。C1は最初の要求の処理に使用されているため(ケース1)、要求にサービスを提供するために新しい物理接続C2が作成されます(ケース2a)。別の要求TC3を受け取ったときに、C1もC2も使用中の場合、TC3は指定された時間待機するか、エラー・メッセージ付きで戻されます(ケース2b)。
パフォーマンスを向上するために、アプリケーションに応じて接続プーリングのパラメータを設定できます。図11-5のパフォーマンス・グラフは、Pro*C/C++のデモ・プログラム:1のCMIN値を変更することで、パフォーマンスが向上することを示しています。デモ・プログラム:2は、CMAXパラメータを変更することで、パフォーマンスが向上することを示しています。
デモ・プログラム: 1のプリコンパイル中には、次の接続プール・パラメータが使用されます。
CMAX = 40 CINCR = 3 CMIN = varying values between 1 to 40 CPOOL = YES CTIMEOUT - Do not set
(物理接続がタイムアウトしないことを示します。)
CNOWAIT - Do not set
(空いている接続を取得するまでスレッドが待機することを示します。詳細は、表11-1「接続プーリングのコマンドライン・オプション」を参照してください。)
この例に必要なその他のコマンドライン・オプションは、次の項で示します。
threads = yes
注意: この例では、スレッド数は40で、データベース操作はローカル・データベースに対して実行されます。 |
CPOOL=NO(接続プーリングを使用しない)を指定した場合、アプリケーションの所要時間は6.1秒でした。これに対し、CPOOL=YES(接続プーリングを使用する)を指定すると、アプリケーションの最小所要時間は1.3秒になりました(CMIN=2の場合)。
どちらの場合も、接続プールによって短縮されるのはCONNECT文の所要時間のみであるため、データベース問合せ操作に要する時間は変わりません。CPOOL=NOを指定した場合、アプリケーションは40個の専用接続を作成します。CPOOL=YESとCMIN=2を指定した場合は、最初に2つの接続を作成し、2つのスレッドが接続に同時にアクセスする場合にのみ、これ以上の接続を作成します。そうでない場合は、すべてのスレッドがこの2つの接続を共有します。このため、アプリケーションでは潜在的に38個の接続が回避され、これにより、接続確立のためのサーバーへのラウンドトリップが38個回避されます。その結果、パフォーマンスは3倍に向上します。
注意: 前述の結果は、Solaris 2.6オペレーティング・システム上でOracleサーバーが1つ稼働する、単一CPUおよび256MB RAM搭載のSparc Ultra60マシンでの測定値です。サーバーとクライアントは同一マシン上で実行されています。 |
CPOOL=YESの線は、接続プーリングが使用可能になっているときのアプリケーションの所要時間を表します。CPOOL=NOの線は、接続プーリングが使用禁止になっているときのアプリケーションの所要時間を表します。
/* * cpdemo1.pc * * Description: * The program creates as many sessions as there are threads. * Each thread connects to the default database, calls COMMIT and * executes a simple SELECT statement. Each thread have its own * runtime contexts. * */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sqlca.h> #define _EXC_OS_ _EXC__UNIX #define _CMA_OS_ _CMA__UNIX #define MAX_ROWS 256 #define BUFF_LEN 21 #ifdef DCE_THREADS #include <pthread.h> #else #include <thread.h> #endif /* Function prototypes */ void err_report(); #ifdef DCE_THREADS void do_transaction(); #else void *do_transaction(); #endif void get_transaction(); void logon(); void logoff(); #define CONNINFO "hr/hr" #define THREADS 40 struct parameters { sql_context * ctx; int thread_id; }; typedef struct parameters parameters; struct timeval tp1; struct timeval tp2; /*************************************** * Main ***************************************/ main() { sql_context ctx[THREADS]; #ifdef DCE_THREADS pthread_t thread_id[THREADS]; pthread_addr_t status; #else thread_t thread_id[THREADS]; int status; #endif parameters params[THREADS]; int i; EXEC SQL ENABLE THREADS; EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); if(gettimeofday(&tp1) == -1) { perror("First: "); exit(0); } /* Create THREADS sessions by connecting THREADS times */ for(i=0;i<THREADS;i++) { printf("Start Session %d....",i); EXEC SQL CONTEXT ALLOCATE :ctx[i]; logon(ctx[i],CONNINFO); } /*Spawn threads*/ for(i=0;i<THREADS;i++) { params[i].ctx=ctx[i]; params[i].thread_id=i; #ifdef DCE_THREADS if (pthread_create(&thread_id[i],pthread_attr_default, (pthread_startroutine_t)do_transaction, (pthread_addr_t) ¶ms[i])) #else if (status = thr_create (NULL, 0, do_transaction, ¶ms[i], 0, &thread_id[i])) #endif printf("Cant create thread %d\n",i); else printf("Created Thread %d\n", i); } /* Logoff sessions....*/ for(i=0;i<THREADS;i++) { /*wait for thread to end */ #ifdef DCE_THREADS if (pthread_join(thread_id[i],&status)) printf("Error when waiting for thread % to terminate\n", i); else printf("stopped\n"); printf("Detach thread..."); if (pthread_detach(&thread_id[i])) printf("Error detaching thread! \n"); else printf("Detached!\n"); #else if (thr_join(thread_id[i], NULL, NULL)) printf("Error waiting for thread to terminate\n"); #endif logoff(ctx[i]); EXEC SQL CONTEXT FREE :ctx[i]; } if(gettimeofday(&tp2) == -1) { perror("Second: "); exit(0); } printf(" \n\nTHE TOTAL TIME TAKEN FOR THE PROGRAM EXECUTION = %f \n\n", (float)(tp2.tv_sec - tp1.tv_sec) + ((float)(tp2.tv_usec - tp1.tv_usec)/1000000.0)); } /*********************************************************************** * Function: do_transaction * Description: This function calls CMMIT and execute a simple SELECT * statement. ***********************************************************************/ #ifdef DCE_THREADS void do_transaction(params) #else void *do_transaction(params) #endif parameters *params; { struct sqlca sqlca; char empName[MAX_ROWS][BUFF_LEN]; int src_count; sql_context ctx=params->ctx; EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); EXEC SQL CONTEXT USE :ctx; printf("Thread %d executing transaction\n",params->thread_id); EXEC SQL COMMIT; EXEC SQL SELECT FIRST_NAME into empName from EMPLOYEES where JOB_ID= (select JOB_ID from EMPLOYEES where EMPLOYEE_ID=205); } /************************************************************** * Function: err_report * Description: This routine prints out the most recent error **************************************************************/ void err_report(sqlca) struct sqlca sqlca; { if (sqlca.sqlcode < 0) printf("\n%.*s\n\n",sqlca.sqlerrm.sqlerrml,sqlca.sqlerrm.sqlerrmc); exit(1); } /************************************************************ * Function: logon * Description: Logs on to the database as USERNAME/PASSWORD ************************************************************/ void logon(ctx,connect_info) sql_context ctx; char * connect_info; { EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); EXEC SQL CONTEXT USE :ctx; EXEC SQL CONNECT :connect_info; printf("Connected!\n"); } /*************************************************** * Function: logoff * Description: This routine logs off the database ***************************************************/ void logoff(ctx) sql_context ctx; { EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); EXEC SQL CONTEXT USE :ctx; EXEC SQL COMMIT WORK RELEASE; }
デモ・プログラム: 2のプリコンパイル中に、次の接続プーリング・パラメータが使用されます。
CMAX = 5〜40の可変値
CINCR = 3
CMIN = 1〜40の可変値
CPOOL = YES
CTIMEOUT-設定しない
(物理接続がタイムアウトしないことを示します。)
CNOWAIT-設定しない
(空いている接続を取得するまでスレッドが待機することを示します。詳細は、表11-1「接続プーリングのコマンドライン・オプション」を参照してください。)
この例に必要なその他のコマンドライン・オプションは、次の項で示します。
threads = yes
次の図は、cpdemo2のパフォーマンス・グラフを示します。
注意: この例では、スレッド数は40で、データベース操作はローカル・データベースに対して実行されます。 |
この例では、CMIN=5、CMAX=14のときに、CPOOL=NOを使用した場合と比べてプログラムの実行速度が約2.3倍に向上し、最善のパフォーマンスが得られます。ただし、接続プーリングを使用可能にすることで実行がさらに高速化する「cpdemo1」ほどの向上は見られません。これは、「cpdemo1」では単純なSELECT文のみを実行しているのに対し、「cpdemo2」ではUPDATE文とSELECT文の両方を実行しているためです。したがって、「cpdemo1」ではデータベース操作の実行よりも接続の作成に時間がかかっています。接続プーリングを使用可能にすると、作成される接続の数が減り、時間が短縮されます。その結果、全体的なパフォーマンスが向上します。「cpdemo2」では、データベース操作の実行に比べて接続の作成にかかる時間が少ないため、全体的なパフォーマンス向上の度合いは低くなります。
次のグラフでは、CPOOL=YESの線は、接続プーリングが使用可能になっているときのアプリケーションの所要時間を表します。CPOOL=NOの線は、接続プーリングが使用禁止になっているときのアプリケーションの所要時間を表します。デモ・プログラム「cpdemo2」では40個のスレッドを作成します。CPOOL=NOオプションの場合は、スレッドはそれぞれサーバーに対する専用接続を確立します。このため、40個の接続が作成されます。CPOOL=YESとCMAX=14を指定して同じデモ・プログラムをビルドすると、最大14の接続が作成されます。これらの接続は40個のスレッドで共有されるため、少なくとも26個の接続が節約され、サーバーへの26回のラウンドトリップが回避されます。
次の2つのグラフは、CMINとCMAXの値をそれぞれ変化させた場合のパフォーマンスを示しています。
CPOOL=NOの場合、アプリケーションでは実行に約7.5秒かかります。CPOOL=YESで、CMIN=8およびCMAX=14の場合、実行時間は4.5秒に短縮されます。このため、パフォーマンスの改善は約1.7倍になります。このパフォーマンスの相違は、データベース操作が違う(SELECT対UPDATE)のためです。これは純粋にサーバー側のアクティビティであり、クライアント側機能である接続プール機能の範囲外です。
前述のグラフの場合、デモ・プログラムはCMIN=5およびCINCR=3で実行され、CMAX=14の場合に最善のパフォーマンスが得られます。CPOOL=NOの場合は実行に約7.4秒かかっています。CPOOL=YESでCMAX=14の場合、実行時間は約3.1秒まで短縮され、パフォーマンスは2.3倍向上しています。
パフォーマンス向上の度合いはCMAXによって異なります。したがって、特定のアプリケーションで最善のパフォーマンスを得るには、最適なパフォーマンスに達するまでCMINとCMAXを変化させる必要があります。
/* * Program to show the performance improvement when cpool option is used * Run this program with cpool=no. Record the time taken for the program to * execute * * Compare the execution time * * This program also demonstrates the impact of properly tuned CMAX * parameter on the performance * * Run the program with the following parameter values for best performance * * CMIN=5 * CINCR=2 * CMAX=14 * */ #include <stdio.h> #include <sqlca.h> #ifdef DCE_THREADS #include <pthread.h> #else #include <thread.h> #endif #define CONNINFO "hr/hr" #define THREADS 40 #define MAX_ROWS 256 #define BUFF_LEN 21 /***** prototypes ************** */ #ifdef DCE_THREADS void selectFunction(); void updateFunction(); #else void *selectFunction(); void *updateFunction(); #endif void err_report(struct sqlca sqlca); /* ************************* */ /***** parameter to the function selectFunction, updateFunction */ struct parameters { sql_context ctx; char connName[20]; char dbName[20]; int thread_id; }; typedef struct parameters parameters; /*******************************************/ parameters params[THREADS]; struct timeval tp1; struct timeval tp2; int main() { int i, status; thread_t thread_id[THREADS]; int thrNos[THREADS]; for(i=0; i<THREADS; i++) thrNos[i] = i; EXEC SQL ENABLE THREADS; /* Time before executing the program */ if(gettimeofday(&tp1) == -1){ perror("First: "); exit(0); } EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); /* connect THREADS times to the data base */ for(i=0; i<THREADS; i++) { strcpy(params[i].dbName, "inst1"); sprintf(params[i].connName,"conn%d", i); params[i].thread_id = i; /* logon to the data base */ EXEC SQL CONTEXT ALLOCATE :params[i].ctx; EXEC SQL CONTEXT USE :params[i].ctx; EXEC SQL CONNECT :CONNINFO AT :params[i].connName USING :params[i].dbName; } /* create THREADS number of threads */ for(i=0;i<THREADS;i++) { printf("Creating thread %d \n", i); if(i%2) { /* do a select operation if the thread id is odd */ #ifdef DCE_THREADS if(pthread_create(&thread_id[i],pthread_attr_default, (pthread_startroutine_t)selectFunction, (pthread_addr_t) ¶ms[i])) #else if(thr_create(NULL, 0, selectFunction, ¶ms[i], 0, &thread_id[i])) #endif printf("Cant create thread %d \n", i); } else { /* othewise do an update operation */ #ifdef DCE_THREADS if(pthread_create(&thread_id[i],pthread_attr_default, (pthread_startroutine_t)updateFunction, (pthread_addr_t) ¶ms[i])) #else if(thr_create(NULL, 0, updateFunction, ¶ms[i], 0, &thread_id[i])) #endif printf("Cant create thread %d \n", i); } } for(i=0; i<THREADS; i++) { #ifdef DCE_THREADS if(pthread_join(thread_id[i],&status)) printf("Error when waiting for thread % to terminate\n", i); if(pthread_detach(&thread_id[i])) printf("Error detaching thread! \n"); #else if(thr_join(thread_id[i], NULL, NULL)) printf("Error waiting for thread i(%d) to terminate\n", i); #endif } if(gettimeofday(&tp2) == -1){ perror("Second: "); exit(0); } printf(" \n\nTHE TOTAL TIME TAKEN FOR THE PROGRAM EXECUTION = %f \n\n", (float)(tp2.tv_sec - tp1.tv_sec) + ((float)(tp2.tv_usec - tp1.tv_usec)/1000000.0)); /* free the context */ for(i=0; i<THREADS; i++) { EXEC SQL CONTEXT USE :params[i].ctx; EXEC SQL AT :params[i].connName COMMIT WORK RELEASE; EXEC SQL CONTEXT FREE :params[i].ctx; } return 0; } #ifdef DCE_THREADS void selectFunction(parameters *params) #else void *selectFunction(parameters *params) #endif { char empName[MAX_ROWS][BUFF_LEN]; printf("Thread %d selecting .... \n", params->thread_id); EXEC SQL CONTEXT USE :params->ctx; EXEC SQL AT : params->connName SELECT FIRST_NAME into empName from EMPLOYEES; printf("Thread %d selected ....\n", params->thread_id); return 0; } #ifdef DCE_THREADS void updateFunction(parameters *params) #else void *updateFunction(parameters *params) #endif { printf(" Thread %d Updating ... \n", params->thread_id); EXEC SQL CONTEXT USE :params->ctx; EXEC SQL AT :params->connName update EMPLOYEES set SALARY = 4000 where DEPARTMENT_ID = 10; /* commit the changes */ EXEC SQL AT :params->connName COMMIT; printf(" Thread %d Updated ... \n", params->thread_id); return 0; } /*********** Oracle error ***********/ void err_report(struct sqlca sqlca) { if (sqlca.sqlcode < 0) printf("\n%.*s\n\n",sqlca.sqlerrm.sqlerrml,sqlca.sqlerrm.sqlerrmc); exit(0); }