Symfoware(R) Server RDBユーザーズガイド 応用プログラム開発編 - FUJITSU -
目次 索引 前ページ次ページ

上へ第2章 データベースを処理する応用プログラムの開発の概要
上へ2.1 SQL埋込みCプログラムの作成方法

2.1.12 マルチスレッド環境におけるデータ操作

ここでは、マルチスレッド環境で動作する応用プログラムを開発する方法について説明します。

インターネット(アプリケーションサーバ)環境で動作する応用プログラムは、多くのクライアント端末からの依頼を受け、繰り返し起動や多重動作できることが必要とされます。このような条件では、高速な起動や資源の節約が期待できるマルチスレッド環境での動作が適しています。

image

マルチスレッド環境で動作する応用プログラムは共有のアドレス・スペースで複数のスレッドを使用します。スレッは、プロセス内で実行されるサブプロセスです。1つのスレッドに対して1つの業務を割り当てることができ、複数のスレッドを使用して複数の業務を並列に実行できます。

最初にSymfoware/RDBをアクセスした時から終了まで、すなわち最初のCONNECT文から最後のDISCONNECT文までをセショと呼びます。1つのプロセスで1つのセションを実行する場合は、最初のSQL文の実行時にSymfoware/RDBが自動的にセション環境を作成しています。しかし、マルチスレッド環境では、複数のセションを同時に実行するため、セションを制御する関数を利用してセションの作成および破棄を行う必要があります。応用プログラム中で複数のセションを制御するために、セションの識別情報としてセションIDを使用します。セションIDは、プロセス内で一意な情報です。

Symfoware/RDBでは、スレッドとセションを対応付け、SQL文の実行環境を複数作成することによって、マルチスレッド環境下で応用プログラムを動作できます。

image

また、セションとスレッドの対応を一度切り離すことによって、無駄なスレッドを減らし、資源を有効に利用できます。

image

■スレッド上でのセションの作成方

セションは、セションを制御する関数を使用して、以下の手順で作成します。

各関数の詳細については、“SQLリファレンスガイド”を参照してください。

image

(1) SQLThrAllocID関数を発行して、セションを作成します。プロセス内でセションを識別するためのセションIDを受け取ります。

(2) SQLThrStartID関数を発行して、セションを開始します。これにより、セションと動作スレッドが対応付けられます。

(3) データベースを検索および更新するSQL文を実行します。

(4) SQLThrEndID関数を発行して、セションと動作スレッドの対応を切り離します。

(5) SQLThrFreeID関数を発行して、セションを破棄します。

■セションとスレッドの使用方

マルチスレッド環境で動作する応用プログラムにおけるセションとスレッドの使用方法には、以下の3種類があります。省資源化およびレスポンスの向上を図るためには、スレッド数がセション数より少ない環境で応用プログラムを実行することを推奨します。なお、作成するスレッドの数に制限はありません。

◆単一セションに対して複数のスレッドが対応する場合

1つのセションに対して複数のスレッドを同時に使用することはできません。セマフォを利用して排他制御を行ってください。

image

image

(1) セション1を作成します。セションIDはses1とします。

(2) CONNECT文を実行します。

(3) セション1とスレッドT1を対応付け、SQL文を実行した後、セション1とスレッドT1の対応を切り離します。1つのセションにおいて、同時に複数のスレッドを実行することはできません。セマフォを利用するなど、排他処理を行ってください。

(4) セション1とスレッドT2を対応付けます。スレッドT1で使用されていたセションID(ses1)が、スレッドT2に渡されます。同様にしてスレッドnまで動作します。

(5) DISCONNECT文を実行します。

(6) セション1を破棄します。

◆スレッド数がセション数以上の場合(スレッド数≧セション数)

この場合、各スレッドごとにセションが作成さているので、排他制御を考慮する必要はありません。ただし、複数スレッドが共通でCPUを使用するため、必ずしもレスポンスを向上できるとは限りません。

image

(1) セション1〜セションnを作成します。

(2) 各セションに対してスレッドT1〜Tnを対応付け、SQL文を実行した後、対応を切り離します。この場合、各スレッドごとに専用のセションがあるので、排他を考慮する必要はありません。

(3) セション1〜セションnを破棄します。

◆スレッド数がセション数より少ない場合(スレッド数<セション数)

2つのスレッドを使用して3つのセションを実行する例を示します。セションとスレッドの対応を一度切り離すことにより、セション数よりも少ないスレッド数で動作できます。このため、資源の共用による省資源化を実現できます。また、ディスクのI/OなどによるCPUの無駄な待ち状態を解消する操作を行うことによって、レスポンスを向上できます。

image

image

(1) セション1〜セション3を作成します。

(2) セション1とスレッドT1およびセション2とスレッドT2をそれぞれ対応付け、SQL文を実行した後、対応を切り離します。

(3) セション1とスレッドT1およびセション3とスレッドT2をそれぞれ対応付け、SQL文を実行した後、対応を切り離します。

(4) セション1における作業が終了したので、今までセション1で使用していたスレッドT1をセション2で使用します。また、セション3とスレッドT2を対応付けます。SQL文を実行した後、対応を切り離します。

(5) セション1〜セション3を破棄します。

■マルチスレッド環境で動作する応用プログラムの

マルチスレッド環境で動作する応用プログラムの例を以下に示します。スレッドを2つ起動し、並列で動作する例です。(Windowsで、以下の例に示した応用プログラムを動作させるためには、翻訳・結合編集時のコマンドのオプションに-DNTを指定してください。Linuxで、以下の例に示した応用プログラムを動作させるためには、翻訳・結合編集時のコマンドのオプションに-DLinuxを指定してください。)

#if defined(NT)
#include <windows.h>
#else
#if defined(Linux)
#include <stdio.h>
#include <pthread.h>
#else
#include <stdio.h>
#include <thread.h>
#endif
#endif
#include "sqlrdbei.h"

#define SQL_STATEMENT1   \
 "SELECT WISTRU1 FROM SI_SCHM1.SI_TABLE WHERE WIUNQU1=1"                     …… (1)

void* sub_threadA(void *);                                                   …… (2)
void* sub_threadB(void *);

int main(void)
{
    SQLRETURN   ret    =0;
    SQLHDBS     ses_id =0;
    SQLHDBS     ses_id2=0;

#if defined(NT)
    unsigned long t_threadA;
    unsigned long t_threadB;
    HANDLE thread_tbl[2];
#else
#if defined(Linux)
    pthread_t t_threadA;
    pthread_t t_threadB;
#else
    thread_t t_threadA;
    thread_t t_threadB;
    thread_t ret_threadA;
    thread_t ret_threadB;
#endif
#endif

    ret = SQLThrAllocID( &ses_id );                                           …… (3)
    if ( ret != SQLRDB_NORMAL ) {
        printf( "SQLThrAllocID(code) = %d\n" , ret );
        return 1;
    }
    ret = SQLThrAllocID( &ses_id2 );
    if ( ret != SQLRDB_NORMAL ) {
        printf( "SQLThrAllocID(code) = %d\n" , ret );
        ret = SQLThrFreeID( ses_id );
        return 1;
    }

#if defined(NT)
    t_threadA = (unsigned long)_beginthread( sub_threadA, 0, &ses_id);         …… (4)
    thread_tbl[0] = (HANDLE)t_threadA;
    t_threadB = (unsigned long)_beginthread( sub_threadB, 0, &ses_id2);
    thread_tbl[1] = (HANDLE)t_threadB;
    WaitForMultipleObjects( 1, thread_tbl, TRUE, INFINITE );                   …… (5)
#else
#if defined(Linux)
    pthread_create(&t_threadA, NULL, sub_threadA, (void *)&ses_id );         …… (4)
    pthread_create(&t_threadB, NULL, sub_threadB, (void *)&ses_id2);
    pthread_join(t_threadA, NULL);                                           …… (5)
    pthread_join(t_threadB, NULL);
#else
    thr_create(NULL, 0, sub_threadA, (void *)&ses_id,  THR_BOUND, &t_threadA); …… (4)
    thr_create(NULL, 0, sub_threadB, (void *)&ses_id2, THR_BOUND, &t_threadB);
    thr_join(t_threadA, &ret_threadA, NULL);                                 …… (5)
    thr_join(t_threadB, &ret_threadB, NULL);
#endif
#endif

    ret = SQLThrFreeID( ses_id );                                             …… (6)
    ret = SQLThrFreeID( ses_id2 );

    return 0;

}

(1) 実行するSQL文を定義します。

(2) スレッド(サブプロセス)の関数を宣言します。

(3) セションを作成します。セションを作成する関数を実行することによって、セションIDを受け取ります。

(4) スレッドを作成して、sub_threadAおよびsub_threadBを実行します。

(5) sub_threadAおよびsub_threadBの両方の処理が終了してからセションを破棄するように制御します。

(6) セションを破棄します。

void* sub_threadA(void *ses_id_p)
{
EXEC SQL BEGIN DECLARE SECTION;
    char    SQLSTATE[6];                                     …… (1)
    char    SQLMSG[256];
    VARCHAR statement[256];
    char    dataA[53];
EXEC SQL END   DECLARE SECTION;

    SQLHDBS     ses_id = *(SQLHDBS *)ses_id_p;
    SQLRETURN   ret=0;

    memset(SQLSTATE, 0x00, 6);
    memset(SQLMSG,   0x00, 256);
    memset(dataA,    0x00, 53);

    ret = SQLThrStartID( ses_id );                             …… (2)
    if ( ret != SQLRDB_NORMAL ) {
        printf( "SQLThrStartID(code) = %d\n" , ret );
        return 0;
    }

    EXEC SQL DECLARE CU1 CURSOR FOR CMD1;                       …… (3)

    EXEC SQL WHENEVER SQLERROR GO TO :CONNECT_ERROR;

    EXEC SQL CONNECT TO DEFAULT;                               …… (4)
    printf( "CONNECT(SQLSTATE) = %s\n" , SQLSTATE );
    printf( "CONNECT(SQLMSG)   = %s\n" , SQLMSG );

    EXEC SQL WHENEVER SQLERROR CONTINUE;

    strcpy( statement.sqlvar, SQL_STATEMENT1 );
    statement.sqllen = strlen( statement.sqlvar );
    EXEC SQL PREPARE CMD1 FROM :statement;
    printf( "PREPARE(SQLSTATE):%s\n", SQLSTATE );
    printf( "PREPARE(SQLMSG):%s\n", SQLMSG );

    EXEC SQL OPEN CU1 ;                                        …… (5)
    printf( "OPEN(SQLSTATE):%s\n", SQLSTATE );
    printf( "OPEN(SQLMSG):%s\n", SQLMSG );

    while ( strcmp(SQLSTATE,"00000")==0 ) {
        EXEC SQL FETCH CU1 INTO :dataA ;                        …… (6)
        printf( "FETCH(DATA):%s\n", dataA );
        printf( "FETCH(SQLSTATE):%s\n", SQLSTATE );
        printf( "FETCH(SQLMSG):%s\n", SQLMSG );
    }

    EXEC SQL CLOSE CU1;                                        …… (7)
    printf( "CLOSE(SQLSTATE):%s\n", SQLSTATE );
    printf( "CLOSE(SQLMSG):%s\n", SQLMSG );

    EXEC SQL DEALLOCATE PREPARE CMD1;
    printf( "DEALLO_PREP(SQLSTATE):%s\n", SQLSTATE );
    printf( "DEALLO_PREP(SQLMSG):%s\n", SQLMSG );

    EXEC SQL COMMIT WORK;                                      …… (8)
    printf( "COMMIT(SQLSTATE) = %s\n" , SQLSTATE ) ;
    printf( "COMMIT(SQLMSG)   = %s\n" , SQLMSG );

    EXEC SQL DISCONNECT ALL;                                   …… (9)
    printf( "DISCONNECT(SQLSTATE) = %s\n" , SQLSTATE );
    printf( "DISCONNECT(SQLMSG)   = %s\n" , SQLMSG );

    ret = SQLThrEndID( ses_id );                              …… (10)
    if ( ret != SQLRDB_NORMAL ) {
        printf( "SQLThrEndID(code) = %d\n" , ret );
        return 0;
    }

    return 0;

CONNECT_ERROR:                                              …… (11)
    printf( "CONNECT(SQLSTATE) = %s\n" , SQLSTATE );
    printf( "CONNECT(SQLMSG)   = %s\n" , SQLMSG );
    ret = SQLThrEndID ( ses_id );
    return 0;

}

(1) SQLSTATEとSQLMSGを各スレッドで宣言します。

(2) セションを開始します。セションを開始する関数を実行することにより、セションとスレッド(sub_threadA)が対応付けられます。

(3) カーソルを宣言します。

(4) サーバとコネクションを接続します。

(5) カーソルをオープンします。

(6) カーソルを位置づけ、その行を読み込みます。

(7) カーソルをクローズします。

(8) トランザクションをCOMMIT文により終了します。

(9) サーバとのコネクションを切断します。

(10) セションを終了します。

(11) サーバとのコネクションに失敗した場合は、セションを終了します。

void* sub_threadB(void *ses_id_p)
{
EXEC SQL BEGIN DECLARE SECTION;
    char    SQLSTATE[6];                                    …… (1)
    char    SQLMSG[256];
EXEC SQL END   DECLARE SECTION;

    SQLHDBS     ses_id = *(SQLHDBS *)ses_id_p;
    SQLRETURN   ret=0;

    memset(SQLSTATE, 0x00, 6);
    memset(SQLMSG,   0x00, 256);

    ret = SQLThrStartID( ses_id );                          …… (2)
    if ( ret != SQLRDB_NORMAL ) {
        printf( "SQLThrStartID(code) = %d\n" , ret );
        return 0;
    }

    EXEC SQL WHENEVER SQLERROR GO TO :CONNECT_ERROR;

    EXEC SQL CONNECT TO 'SI_DB333';                          …… (3)
    printf( "CONNECT(SQLSTATE) = %s\n" , SQLSTATE );
    printf( "CONNECT(SQLMSG)   = %s\n" , SQLMSG );

    EXEC SQL WHENEVER SQLERROR CONTINUE;

    EXEC SQL INSERT INTO SI_SCHM1.SI_TABLE2
             VALUES(1,1,1,1,1,1,1,1,1,1,'R','DD','BBBB');    …… (4)
    printf( "INSERT(SQLSTATE):%s\n", SQLSTATE );
    printf( "INSERT(SQLMSG):%s\n", SQLMSG );

    EXEC SQL COMMIT WORK;                                    …… (5)
    printf( "COMMIT(SQLSTATE) = %s\n" , SQLSTATE ) ;
    printf( "COMMIT(SQLMSG)   = %s\n" , SQLMSG );

    EXEC SQL DISCONNECT ALL;                                 …… (6)
    printf( "DISCONNECT(SQLSTATE) = %s\n" , SQLSTATE );
    printf( "DISCONNECT(SQLMSG)   = %s\n" , SQLMSG );

    ret = SQLThrEndID( ses_id );                             …… (7)
    if ( ret != SQLRDB_NORMAL ) {
        printf( "SQLThrEndID(code) = %d\n" , ret );
        return 0;
    }

    return 0;

CONNECT_ERROR:                                               …… (8)
    printf( "CONNECT(SQLSTATE) = %s\n" , SQLSTATE );
    printf( "CONNECT(SQLMSG)   = %s\n" , SQLMSG );
    ret = SQLThrEndID( ses_id );
    return 0;

}

(1) SQLSTATEとSQLMSGを各スレッドで宣言します。

(2) セションを開始します。セションを開始する関数を実行することにより、セションとスレッド(sub_threadB)が対応付けられます。

(3) サーバとコネクションを接続します。

(4) INSERT文を実行して値を挿入します。

(5) トランザクションをCOMMIT文により終了します。

(6) サーバとのコネクションを切断します。

(7) セションを終了します。

(8) サーバとのコネクションに失敗した場合は、セションを終了します。

■マルチスレッド環境で動作する応用プログラムを作成する場合の注意事

応用プログラムを作成するには、ホスト変数および複数スレッド間の排他などを考慮して、実行したSQL文によるデータベースの検索および更新結果が正しくなるようにしなければなりません。以下に注意点を示します。

状態変数およびメッセージ変数の宣言については、“名前の一意性と有効範囲”を参照してください。


目次 索引 前ページ次ページ

All Rights Reserved, Copyright (C) 富士通株式会社 2003-2004