11 マルチスレッド・アプリケーション

使用中の開発プラットフォームでスレッドがサポートされない場合、この章は無視してください。この章のトピックは、次のとおりです:

11.1 スレッド

マルチスレッド・アプリケーションでは、共有のアドレス空間で複数のスレッドが実行されます。スレッドはプロセス内で実行される軽量のサブプロセスです。コードとデータ・セグメントは共有しますが、独自のプログラム・カウンタ、マシン・レジスタおよびスタックがあります。グローバル変数と静的変数はすべてのスレッドに共通であり、通常、アプリケーション内の複数のスレッドからこれらの変数へのアクセスを管理するには、相互排他メカニズムが必要です。 Mutexは、データの整合性が保たれることを保証する同期化メカニズムです。

mutexの詳細は、マルチスレッドの説明を参照してください。マルチスレッド・アプリケーションの詳細は、スレッド・ファンクションのマニュアルを参照してください。

Pro*C/C++プリコンパイラは、マルチスレッドのOracleサーバー・アプリケーションの開発を(マルチスレッド・アプリケーションをサポートするプラットフォームで)サポートします。サポートされている機能は次のとおりです。

  • スレッド・セーフなコードを生成するコマンドライン・オプション

  • マルチスレッド処理をサポートする埋込みSQL文およびディレクティブ

  • スレッド・セーフなSQLLIBと、その他のクライアント側Oracleライブラリ

    ノート:

    プラットフォームによりサポートするスレッド・パッケージが異なるため、使用しているプラットフォーム固有のOracleマニュアルを参照して、Oracleがスレッド・パッケージをサポートしているかを調べてください。

次の各項目では、前述の機能を使用してマルチスレッドのPro*C/C++アプリケーションを開発する方法を説明します。

  • マルチスレッド・アプリケーションのランタイム・コンテキスト

  • ランタイム・コンテキストを使用する2つのモデル

  • マルチスレッド・アプリケーションのユーザー・インタフェース

  • Pro*C/C++を使用してマルチスレッド・アプリケーションを作成する場合のプログラミング上の考慮事項

  • マルチスレッドのPro*C/C++アプリケーションの例

11.2 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を参照してください。

図11-1 接続とスレッドの疎結合

図11-1の説明が続きます
「図11-1 接続とスレッドの疎結合」の説明

11.3 ランタイム・コンテキストの使用モデル

マルチスレッドのPro*C/C++アプリケーションでランタイム・コンテキストを使用した2つの可能なモデルを次に示します。

  • 単一のランタイム・コンテキストを共有する複数のスレッド

  • 複数のランタイム・コンテキストを使用する複数のスレッド

いずれのモデルを使用した場合も、複数のスレッドで同時に1つのランタイム・コンテキストを共有することはできません。複数のスレッドで同じランタイム・コンテキストを同時に使用すると、ランタイム・エラーが発生します。

11.3.1 複数のスレッドで1つのランタイム・コンテキストを共有

図11-2は、マルチスレッド環境で実行されるアプリケーションを示しています。1つ以上のSQL文を処理するために、様々なスレッドが1つのランタイム・コンテキストを共有します。この場合も、同時に複数のスレッドでランタイム・コンテキストを共有することはできません。図11-2のmutexは、同時使用を防ぐ方法を示しています。

図11-2 スレッド間のコンテキスト共有

図11-2の説明が続きます
「図11-2 スレッド間のコンテキスト共有」の説明

11.3.2 複数のスレッドで複数のランタイム・コンテキストを共有

図11-3に、複数のランタイム・コンテキストを使用して複数スレッドを実行するアプリケーションを示します。この場合、各スレッドは専用のランタイム・コンテキストを使用するため、アプリケーションにはmutexは必要ありません。

図11-3 スレッド間のコンテキスト非共有

図11-3の説明が続きます
「図11-3 スレッド間のコンテキスト非共有」の説明

11.4 マルチスレッド・アプリケーションのユーザー・インタフェース機能

Pro*C/C++プリコンパイラは、次に示すユーザー・インタフェース機能によってマルチスレッド・アプリケーションをサポートしています。

  • コマンドライン・オプションTHREADS=YES|NO

  • 埋込みSQL文とディレクティブ

  • スレッド・セーフなSQLLIBパブリック関数

11.4.1 THREADSオプション

THREADS=YESをコマンドラインに指定すると、ガイドラインに従っている場合、生成されたコードがスレッド・セーフであることがPro*C/C++プリコンパイラにより保証されます。THREADS=YESと指定すると、Pro*C/C++ではすべてのSQL文がユーザー定義のランタイム・コンテキスト範囲内で実行されることが検証されます。プログラムがこの要件を満たしていないと、プリコンパイラ・エラーが戻されます。

11.4.2 埋込み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文にデフォルト(グローバル)ランタイム・コンテキストが使用されます。

11.4.2.1 EXEC SQL ENABLE THREADS

この実行SQL文は、複数のスレッドをサポートするプロセスを初期化します。このSQL文は、マルチスレッド・アプリケーション内の最初の実行SQL文にしてください。

ノート:

Pro*C/C++プリコンパイラとXAを併用する場合は、XAのマルチスレッドを使用する必要があります。EXEC SQL ENABLE THREADS文を使用してPro*Cのマルチスレッドを使用するとエラーになります。

11.4.2.2 EXEC SQL CONTEXT ALLOCATE

この実行SQL文は指定されたランタイム・コンテキストにメモリーを割り当てて初期化します。ランタイム・コンテキスト変数はsql_context型として宣言する必要があります。

11.4.2.3 EXEC SQL CONTEXT USE

このディレクティブはプリコンパイラに、後続の実行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 */
}
11.4.2.4 EXEC SQL CONTEXT FREE

この実行SQL文は、指定したランタイム・コンテキストに関連付けられたメモリーを解放し、ホスト・プログラム変数にNULLポインタを入れます。

11.4.3 CONTEXT USEの例

次に示すコード例では、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.               */
   ...
}

11.4.4 プログラミングの考慮事項

OracleはSQLLIBコードがスレッド・セーフであることを保証しますが、Pro*C/C++のソース・コードがスレッドで正しく動作するように設計する必要があります。たとえば、静的変数とグローバル変数は慎重に使用してください。

また、マルチスレッド・アプリケーションの設計時には次の点に注意してください。

  • SQLCAをスレッド・セーフな構造体として宣言します。通常は自動変数として、ランタイム・コンテキストごとに1つずつ宣言します。

  • SQLDAをスレッド・セーフな構造体として宣言します(SQLCAと同様)。通常は、自動変数としてランタイム・コンテキストごとに1つずつ宣言します。

  • スレッド・セーフ方式でホスト変数を宣言するときは、静的ホスト変数およびグローバル・ホスト変数の使用に注意してください。

  • 複数のスレッドでランタイム・コンテキストを同時に使用するのを避けます。

  • デフォルトのデータベース接続を使用するか、あるいはAT句を使用して明示的に定義するかを判断します。

さらに、複数の埋込みSQLの実行文(EXEC SQL UPDATEなど)をランタイム・コンテキストで同時に未解決にしないでください。

プリコンパイル済アプリケーションの既存の要件も適用されます。たとえば、特定のカーソルへの参照はすべて同じソース・ファイルに含まれている必要があります。

11.5 マルチスレッドの例

次のプログラムは、マルチスレッドの埋込み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) &params[i]))
#else
    if (status = thr_create
    (NULL, 0, do_transaction, &params[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.6 接続プーリング

接続プールとは、いくつかの接続間で再使用できる、データベースへの物理接続グループです。接続プーリング機能の目的は、それぞれの接続に専用接続を使用させないことでパフォーマンスを改善し、リソースの使用を削減することです。

図11-4は、接続プーリングの機能を示します。この例では、アプリケーションの4つのスレッドが接続プールを使用してデータベースと対話しています。この接続プールには物理接続が2つあります。異なるランタイム・コンテキストを使用する4つのスレッドにより、接続プール・ハンドルが使用されます。

図11-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の空いている方を取得します。要求に対してサービスが提供された後、選択されていた接続は接続プール内で再び使用可能になるため、別の名前付き接続または仮想接続がその物理接続を利用できます。

11.6.1 接続プーリング機能の使用方法について

この項には、次の項目が含まれます。

11.6.1.1 接続プーリングを使用可能にする方法

アプリケーションのプリコンパイル中に接続プーリングを使用可能にするには、CPOOL=YESコマンドライン・オプションを設定する必要があります。CPOOL=YES/NOの設定に基づいて、接続プーリング機能が使用可能または使用禁止になります。

ノート:

  • デフォルトでは、CPOOLNOに設定されます。つまり、接続プーリング機能は使用禁止です。この機能をインラインで使用可能または禁止にすることはできません。

  • CPOOLYESに設定されていても、外部オペレーティング・システム認証では接続プールは作成されません。

11.6.1.2 接続プーリングのコマンドライン・オプション

表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が設定されていない場合、要求は接続を取得できるまで待機します。それ以外の場合は、「ORA 24401: これ以上の接続は開けません」というエラー・メッセージが表示されます。

11.6.1.3

次の例の説明は、図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.6.1.4 パフォーマンス・チューニング

パフォーマンスを向上するために、アプリケーションに応じて接続プーリングのパラメータを設定できます。図11-5のパフォーマンス・グラフは、Pro*C/C++のデモ・プログラム:1のCMIN値を変更することで、パフォーマンスが向上することを示しています。デモ・プログラム:2は、CMAXパラメータを変更することで、パフォーマンスが向上することを示しています。

11.6.2 デモ・プログラム: 1

デモ・プログラム: 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マシンでの測定値です。サーバーとクライアントは同一マシン上で実行されています。

図11-5 パフォーマンス・グラフ

パフォーマンス・グラフ

CPOOL=YESの線は、接続プーリングが使用可能になっているときのアプリケーションの所要時間を表します。CPOOL=NOの線は、接続プーリングが使用禁止になっているときのアプリケーションの所要時間を表します。

11.6.2.1
/* 
*  cpdemo1.pc 
* 
* Description: 
*      The program creates as many sessions as there are threads. 
*      Each thread connects to the default database and executes the 
*      SELECT statement 5 times. Each thread has its own runtime context.
* 
*/ 

#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 <pthread.h> 
typedef void*       pthread_addr_t;
typedef void*      (*pthread_startroutine_t) (void*);
#define pthread_attr_default  (const pthread_attr_t *)NULL
#endif 

/* Function prototypes */ 
void   err_report(); 
void   do_transaction(); 
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]; 
  pthread_t thread_id[THREADS]; 
  pthread_addr_t status; 
  parameters params[THREADS]; 
  int i; 

  EXEC SQL ENABLE THREADS; 
  EXEC SQL WHENEVER SQLERROR DO err_report(sqlca); 

  if(gettimeofday(&tp1, (void*)NULL) == -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; 

    if (pthread_create(&thread_id[i],pthread_attr_default, 
      (pthread_startroutine_t)do_transaction, 
      (pthread_addr_t) &params[i])) 
      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 */ 
    if (pthread_join(thread_id[i],&status)) 
      printf("Error when waiting for thread % to terminate\n", i); 
    else 
      printf("stopped\n"); 

    if(i==THREADS-1) 
    {
      logoff(ctx[i]); 
      EXEC SQL CONTEXT FREE :ctx[i];
    } 
  } 

  if(gettimeofday(&tp2, (void*)NULL) == -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 functions executes SELECT 5 times and calls COMMIT.
***********************************************************************/ 
void do_transaction(params) 
parameters *params; 
{ 
  struct sqlca sqlca; 
  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 count(*) into :src_count from EMPLOYEES;
  EXEC SQL SELECT count(*) into :src_count from EMPLOYEES;
  EXEC SQL SELECT count(*) into :src_count from EMPLOYEES;
  EXEC SQL SELECT count(*) into :src_count from EMPLOYEES;
  EXEC SQL SELECT count(*) into :src_count from EMPLOYEES;
} 

/************************************************************** 
* 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");
}

11.6.3 デモ・プログラム: 2

デモ・プログラム: 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の値をそれぞれ変化させた場合のパフォーマンスを示しています。

11.6.3.1 ケース1: CMINを変更

図11-6 ケース1のパフォーマンス・グラフ

ケース1のパフォーマンス・グラフ

CPOOL=NOの場合、アプリケーションでは実行に約7.5秒かかります。CPOOL=YESで、CMIN=8およびCMAX=14の場合、実行時間は4.5秒に短縮されます。このため、パフォーマンスの改善は約1.7倍になります。このパフォーマンスの相違は、データベース操作が違う(SELECT対UPDATE)のためです。これは純粋にサーバー側のアクティビティであり、クライアント側機能である接続プール機能の範囲外です。

11.6.3.2 ケース2: CMAXを変更

図11-7 ケース2のパフォーマンス・グラフ

ケース2のパフォーマンス・グラフ

前述のグラフの場合、デモ・プログラムはCMIN=5およびCINCR=3で実行されました。最善のパフォーマンスはCMAX=14の場合に得られます。CPOOL=NOの場合は実行に約7.4秒かかっています。CPOOL=YESでCMAX=14の場合、実行時間は約3.1秒まで短縮され、パフォーマンスは2.3倍向上しています。

パフォーマンス向上の度合いはCMAXによって異なります。したがって、特定のアプリケーションで最善のパフォーマンスを得るには、最適なパフォーマンスに達するまでCMINとCMAXを変化させる必要があります。

11.6.3.3
/* 
*  cpdemo2.pc 
  * Program to show the performance imcrement when the 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 a properly tuned CMAX 
  * parameter on the performance
  *
  * Run the program with the following parameter values
  * 
  * CMIN=5
  * CINCR=2
  * CMAX=20
  *
  */

#include <stdio.h>
#include <sqlca.h>

#ifdef DCE_THREADS 
#include <pthread.h> 
#else 
#include <sys/time.h>
#include <pthread.h> 
typedef void*       pthread_addr_t;
typedef void*      (*pthread_startroutine_t) (void*);
#define pthread_attr_default  (const pthread_attr_t *)NULL
#endif 


#define CONNINFO "hr/hr"
#define THREADS 40

/***** prototypes ************** */
void selectFunction();
void updateFunction();

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;
  pthread_t thread_id[THREADS];
  pthread_addr_t status;

  int thrNos[THREADS];

  for(i=0; i<THREADS; i++)
    thrNos[i] = i;

  EXEC SQL ENABLE THREADS;

  /* Time before executing the program */
  if(gettimeofday(&tp1, (void*)NULL) == -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, "");
    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 */
        if(pthread_create(&thread_id[i],pthread_attr_default, 
            (pthread_startroutine_t)selectFunction, 
            (pthread_addr_t) &params[i])) 
        printf("Cant create thread %d \n", i);
      }
      else
      {
        /* otherwise do an update operation */
        if(pthread_create(&thread_id[i],pthread_attr_default, 
            (pthread_startroutine_t)updateFunction, 
            (pthread_addr_t) &params[i])) 
        printf("Cant create thread %d \n", i);
      }
  }

  for(i=0; i<THREADS; i++)
  {
    if(pthread_join(thread_id[i],&status)) 
      printf("Error when waiting for thread % to terminate\n", i); 

  }

  if(gettimeofday(&tp2, (void*)NULL) == -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;
}

void selectFunction(parameters *params)
{
  struct sqlca sqlca;
  char empName[110][21];
  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;
}

void updateFunction(parameters *params)
{
  struct sqlca sqlca;
  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);
}