マルチスレッドアプリケーションの作成

マルチスレッドアプリケーションを作成するときには、アプリケーション内の各プログラムをどのように動作させるか、つまり、標準 COBOL プログラム、シリアルプログラム、または再入可能なプログラムのうち、どの種類のプログラムを使用するかを考慮する必要があります。 これら 3 種類のプログラムの中で、REENTRANT(1) プログラムとしてコンパイルされたプログラムには特に注意が必要です。

また、次の事項も考慮する必要があります。

マルチスレッドプログラミングに関する一般的な問題についても知る必要があります。

マルチスレッドアプリケーション用ランタイムシステム

マルチスレッドアプリケーション用ランタイムシステムでは、マルチスレッドアプリケーションとシングルスレッドアプリケーションの両方を処理する必要があります。 その結果、マルチスレッド用ランタイムシステムでは、標準のマルチスレッド COBOL アプリケーションで必要とする多くの同期処理コストがかかります。 さらに、ほとんどの場合に、これらのコストはアプリケーションがマルチスレッドまたはシングルスレッドかどうかに関わらず必要です。 このために、さらにシングルスレッドアプリケーションを最高速度で実行するために、Micro Focus 社では 2 つのランタイムシステムを用意しました。1 つはすべてのアプリケーションをサポートするマルチスレッド用ランタイムシステム、もう 1 つはシングルスレッドアプリケーションのみをサポートするシングルスレッド用ランタイムシステムです。 アプリケーションの実行時、またはアプリケーションのリンク時にどちらのランタイムシステムを使用するかを選択できます。

アプリケーションと一緒に配布するファイルについては、アプリケーションがシングルスレッド用ランタイムシステムまたはマルチスレッド用ランタイムシステムのどちらにリンクされているかによって異なります。 詳細については、ヘルプトピック『使用するランタイムファイルの決定』を参照してください。

再入可能なプログラム作成時の考慮事項

すべてのプログラムを再入可能なプログラムとしてコンパイルできるとは限りません。 使用する COBOL 機能によっては、再入可能なプログラムのコンパイルを妨げることがあります。ANSI 標準の COBOL に含まれるこのような COBOL 機能は古いので、使用しないでください。 次の機能を使用するプログラムは、REENTRANT 指令を指定してコンパイルすることはできません。

これ以外に、再入可能なプログラムを作成する場合に注意する必要のある制約は、次のとおりです。

次に挙げる性能とリソースに関する弊害は、マルチスレッドアプリケーションの再入可能な COBOL プログラムで発生します。

マルチスレッドライブラリルーチン

名前による呼び出しライブラリルーチンは、プログラミングインターフェイスであり、これを介してアプリケーションにマルチスレッドを実装し、制御できます。 ライブラリルーチンは次の目的で使用します。

スレッド制御ルーチン

スレッドを制御するライブラリルーチンを使用して、別のスレッドの終了を待ち、その戻り値を獲得するスレッドを実装できます。

これらのライブラリルーチンを使用して作成したスレッドでは、次の処理が必要になります。

スレッドはスレッド制御ルーチン以外で作成される場合があります。 ただし、このようなスレッドも、ランタイムシステムサービスを使用すると、ランタイムシステムで認識されます。 これらのスレッドにはスレッド制御ライブラリルーチンを通してアクセスできます。スレッドが使用するライブラリルーチンは次のとおりです。

複数言語を使用する環境で、ランタイムシステムを使用しているためにランタイムシステムに認識されているスレッドが、そのランタイムシステムまたはスレッド制御ルーチン以外で作成されている場合は、次のどれかを実行する必要があります。

アプリケーションは、CBL_GET_OS_INFO または CBL_THREAD_SELF を呼び出して、プログラムが使用するランタイムシステムがスレッド制御ルーチンをサポートするかどうかを確認できます。

例 - プログラムがサポートするランタイムシステムの確認
     call "CBL_THREAD_SELF" using thread-id
      on exeception
*       cbl_thread ルーチンはサポートされていません。
     end-call
     if  return-code = 1008
*       シングルスレッド専用 rts で実行中です。
     end-if

スレッド同期ルーチン

Net Express には、モニタ、セマフォ、ミューテックス、およびイベントでの 4 種類の同期オブジェクトが用意されています。 これら 4 種類の同期オブジェクトの制御は COBOL 構文と名前による呼び出しライブラリルーチンによって行われます。

スレッド固有データを処理するルーチン

ランタイムライブラリルーチン CBL_ALLOC_THREAD_MEM とライブラリルーチン CBL_TSTORE_n を使用して、スレッドの作成中にしか存在しない動的データ、選択したスレッドローカルデータまたは外部スレッドローカルデータを作成できます。

アプリケーションの初期化

マルチスレッドアプリケーションの初期化にはいくつかの注意が必要です。 正しいプログラミング方法としては、最初に、アプリケーションがマルチスレッドをサポートするランタイムシステムで動作しているかどうかを決定する必要があります。 次の例を参照してください。

例 - ランタイムシステムの確認
 Working-Storage Section.
 01 thread-id     usage pointer.
 . . .
* シングルスレッドモードで実行される初期化コードです。
* このコードで、ランタイムシステムがマルチスレッド。または、
* CBL_THREAD_ ルーチンをサポートしているかを確認します。
     call 'CBL_THREAD_SELF' using thread-id
      on exception
*        cbl_thread ルーチンはサポートされていません。
     end-call
     if  return-code = 1008
*        シングルスレッド rts で実行中です。
     end-if

この例では、ランタイムライブラリルーチン CBL_THREAD_SELF を使用します。 COBOL 構文では、マルチスレッドの多くの機能を使用できますが、COBOL ランタイムライブラリルーチンでもすべての機能を使用できます。 ランタイムライブラリルーチンでは、COBOL 構文では使用できない高度なマルチスレッドプログラミング機能を使用できます。

ランタイムシステムがマルチスレッドをサポートしていることをアプリケーションが認識した場合は、同期プリミティブのすべてを適切な OPEN 文によって初期化する必要があります。 最も簡単な方法は、アプリケーション内で他のスレッドが作成される前に、主プログラムの一部として初期化を行う方法です。 ただし、アプリケーション設計、モジュール構成、または複数言語などの理由により、この方法で初期化ができない場合には、Micro Focus 社が提供する各プログラムごとの初期化済みミューテックスを使用できます。 このミューテックスには、CBL_THREAD_PROG_LOCK と CBL_THREAD_PROG_UNLOCK ランタイムライブラリルーチンを使用してアクセスします。 これらのルーチンを使用すると、プログラムのローカル同期プリミティブのハンドルをアプリケーションの実行中に確実に 1 回のみ初期化できます。

例 - ライブラリルーチンを使用したスレッドのロック

次のコードでは、ライブラリルーチンを使用して、プログラムをマルチスレッドモードで実行中に初期化する例を示します。

 Working-Storage Section.
 01 first-flag         pic x comp-x value 1.
    88 first-time       value 1.
    88 not-first-time   value 0.
	 
* マルチスレッドモードで実行される初期化コードです。
* プログラムのローカルデータは確実かつ適切に初期化されます。

     if first-time then
         call 'CBL_THREAD_PROG_LOCK'
         if first-time then
* プログラムのローカルデータと同期オブジェクトを初期化します。
               ...
             set not-first-time   to true
         end-if
         call 'CBL_THREAD_PROG_UNLOCK'
     end-if

レベル-88 のデータ項目 first-time を二重チェックしていることに注目してください。 これは、マルチスレッドアプリケーションの優れた最適化方法です。 最適化の目的は、意図するアクションがすでに実行されている場合にミューテックスをロックするオーバヘッドをなくすことです。 この例では、複数のスレッドが、正しく初期化される前のプログラムを実行すると、first-time が真であることを検出し、CBL_THREAD_PROG_LOCK を呼び出します。 ただし、first-time が真の間に 1 つのロックのみはロックを獲得します。 ロックを取得したスレッドが初期化を行います。

初期化を終了し、初期化を行ったスレッドがロックを取得してる間に、first-time フラグが偽に設定されます。 これ以後にロックを取得しようとするスレッドは、first-time が偽なので初期化がすでに完了していると判断し、プログラムのロックをただちに解放します。 初期化後に起動されたスレッドはすべて、first-time フラグが偽のために、プログラムロックの取得を行いません (そのため、プログラムエントリのオーバヘッドが減ります)。

初期化が行われているかどうかを示すフラグは、可能な限り簡単なものにしてください (1 バイトのデータ項目など)。

スレッドの操作

マルチスレッド用ランタイムシステムは、START 文または CBL_THREAD_n ライブラリルーチンを使用して、ランタイムシステム自身が作成したスレッドをサポートします。また、オペレーティングシステムによって直接作成された他言語のスレッドもサポートします。 COBOL 構文および CBL_THREAD_n ルーチンは汎用性があるため、スレッドの操作に適しています。 さらに、CBL_THREAD_n ルーチンは、他言語で作成されたプログラムからも呼び出せます。そのため、マルチスレッドアプリケーションはランタイムシステムの高度な機能を十分に利用できます。

スレッドハンドル

一般に、ランタイムシステムはスレッドハンドルでスレッドを識別します。 スレッドハンドルは、次のモジュールとスレッドで使用します。

スレッドハンドルは、各種のマルチスレッド COBOL 文または CBL_THREAD_n ルーチンに対して、一意にスレッドを識別します。 十分に注意を払えば、作成スレッドのみでなく、実行単位に含まれるアクティブなスレッドでもスレッドハンドルを使用できます。 スレッドの存在期間とそのスレッドハンドルの存在期間は必ずしも同じではないことを覚えておいてください。 スレッドの作成方法やスレッドに対する処理によっては、スレッドが終了してもそのスレッドハンドルがまだ有効な場合があります。 システムがそのハンドルに関連付けられたリソースを回復するために、スレッドのハンドルを明示的にデタッチするかどうかはユーザが判断します。

スレッドをハンドルからデタッチする方法はいくつかあります。 スレッドが START 文または CBL_THREAD_CREATE ルーチンで作成された場合は、デフォルトでは、作成されたハンドルはデタッチされます。 また、他言語で作成したスレッドがランタイムシステムで認識されると、作成されるハンドルは常に自動的にデタッチされます。 さらに、CBL_THREAD_DETACH を呼び出すと、デタッチされていないハンドルをデタッチできます。 どの場合も、デタッチされたハンドルは、そのスレッドが終了するとすぐに無効になるので常に注意して使用する必要があります。 ハンドルがデタッチされていないスレッドがすでに終了している場合には、そのスレッドハンドルで CBL_THREAD_DETACH を呼び出すと、ただちにそのハンドルは無効になります。

デタッチされていないハンドルは、戻り値を獲得する場合 (WAIT 文または CBL_THREAD_WAIT ルーチンを使用)、およびスレッドの終了後に (CBL_THREAD_IDDATA_GET ルーチンを使用) スレッド識別データを検査する場合に便利です。 START 文を指定すると、デタッチされていないスレッドハンドルを戻し、新規作成されたスレッドを識別できます。 CBL_THREAD_CREATE ルーチンを指定すると、スレッドが作成されてデタッチされていないハンドルが戻されたかどうかを示すフラグを使用できます。

スレッドの作成と終了

スレッドは次のどれかを使用して作成します。

最初の 2 つの方法で作成されたスレッドは COBOL スレッドと呼ばれます。 オペレーティングシステムによって直接作成されたスレッドは他言語スレッドと呼ばれます。

COBOL スレッドの作成と操作が汎用的で優れているので、ここでは、COBOL スレッドについてのみ説明します。

スレッドの開始点は、ネストされてないプログラム名、COBOL エントリポイント、または C 言語などの他言語で記述された外部ルーチンであることが必要です。ネストされたプログラム名、節名、または段落名を開始点とすることはできません。

開始点の名前は、テキスト文字列で指定できます。 テキスト文字列を使用してエントリポイントを見つける処理のオーバヘッドは、CALL 識別子 を検索するのと同じことです。 START 文に手続きポインタを使用すると、このオーバヘッドを回避できます。

作成された各スレッドの開始点のパラメータは 1 つのみです。 このパラメータを渡すときに BY CONTENT 指定を使用すると、システムはスレッドを作成する前にコピーを作成し、呼び出し側のスレッドが START 文からの戻り値に基づいて元のパラメータを自由に変更できるようにします。

ハンドルがデタッチされていないスレッドを作成 (IDENTIFIED BY 句を使用) すると、WAIT 文を使用して戻り値を後で取得できます。 WAIT 文の終了後、指定したスレッドハンドルは無効となり、そのスレッドに関連付けられたリソースはすべて解放されます。

作成されたスレッドで STOP RUN RETURNING 文を使用すると、実行単位は終了しません。この文は戻り値を返し、スレッドを終了するのみです。 これは、次のコードと同等です。

call 'CBL_THREAD_EXIT' using by value address of thread-parm.

他言語スレッドの STOP RUN、または CBL_THREAD_CREATE 以外で作成された実行単位の主スレッドは、アクティブな COBOL スレッドがすべて終了するのを待って、実行単位を終了します。

スレッドからの戻り値は常にポインタです。 このポインタにより、簡単なデータ構造体と複雑なデータ構造体の両方を返すことができます。

スレッドは、STOP RUN を実行するか、または、CBL_THREAD_EXIT を呼び出してスレッド自身を終了できます。 また、通常、EXIT PROGRAM または GOBACK により開始点のプログラムが終了すると、スレッドも終了します。 また、他のスレッドが COBOL スレッドを終了するのに役立つ場合もあります。 たとえば、CBL_THREAD_KILL ルーチンを使用すると、COBOL スレッドが終了します。

例 - スレッドの作成と終了
$set reentrant

 Data Division.
 Working-Storage Section.
 01 thread-handle           usage pointer.
 01 thread-return           usage pointer.
 Linkage Section.
 01 thread-parm             picture x(32).
 01 thread-return-record    picture x(32).
 Procedure Division.
*> 開始点
     call 'CBL_THREAD_CREATE'	
           using    'CREATED'
                    'This is a 32 character parameter'
           by value 0  *> オプションパラメータのサイズ
                    1   *> デタッチしないためのフラグ
                    0  *> デフォルトの優先順位
                    0  *> デフォルトスタック
           by reference  thread-handle
     if  return-code = 0
         call 'CBL_THREAD_WAIT'	using
                             by value thread-handle
                             by reference thread-return
         set address of thread-return-record
                                          to thread-return
         display thread-return-record
     end-if
     stop run.
Entry "CREATED" using thread-parm.
     display thread-parm
     stop run returning address of thread-parm.

このアプリケーションは、開始点が CREATED であるスレッドを作成します。 作成されたスレッドはパラメータを表示し、親スレッドで使用するためにそのパラメータのアドレスを返します。 CREATED スレッドの STOP RUN RETURNING は、実行単位を終了しません。かわりに、戻り値を返し、スレッドを終了します。 これは、次のコードと同等です。

call 'CBL_THREAD_EXIT' using by value address of thread-parm.

スレッドの取り消し

CBL_THREAD_CREATE で作成されたスレッドは、CBL_THREAD_KILL ルーチンで取り消すことができます。 CBL_THREAD_KILL を使用して、別の方法で作成されたスレッドを強制終了できません。 この場合には、COBOL スレッドはただちに異常終了されますが、通常は、CBL_THREAD_KILL を一般的なアプリケーションスレッドコントロールの一部として使用しないでください。 主な理由は、スレッドの終了時に、ユーザとシステムの同期リソースが、正しくロック解放されないためです。 そのため、ユーザアプリケーションとランタイムシステムの間の同期が影響を受け、アプリケーションで重大な問題が発生することがあります。

CBL_THREAD_KILL は、主スレッドの重大エラーハンドラでは、適切に使用できます。 このエラーハンドラでは、スレッドハンドルを CBL_THREAD_LIST_n ルーチンによって獲得できます。スレッドをすべて取り消し、STOP RUN でアプリケーションを終了します。 この方法では、同期プリミティブをロックする必要性が最小限になるため、CBL_THREAD_KILL のランダム使用に比べると危険ではありません。ただし、実行単位の終了時にファイル破壊やデッドロックが発生する可能性はあります。

いずれにせよ、ほとんどのアプリケーションでは CBL_THREAD_KILL を使用しないようにしてください。 そのためには、終了フラグをもつスレッド識別データを作成 (CBL_THREAD_IDDATA_ALLOC ルーチンを使用) します。 そのデータは、ロックが保持されていないレベルの各スレッドでポーリングできます。 終了フラグが設定されている場合は、ポーリングスレッドを正常に終了できます。

例 - スレッド識別データと終了フラグの作成
******************** MAINPROG.CBL ********************
 identification division.
 program-id. mainprog.

 Data Division.
 Local-Storage Section.
 01  iddata-ptr              usage pointer.
 01  sub-iddata-ptr          usage pointer.
 01  sub-handle              usage pointer.
 Linkage Section.
 01  iddata-record.
     05  iddata-name         pic x(20).
     05  iddata-term         pic x comp-x value 0.

 Procedure Division.

* 識別データを確立します。- 識別データの割り当て時に 
* データは初期化されません。
* ポインタの取得後に初期化を行います。
 
     call 'CBL_THREAD_IDDATA_ALLOC' using 
                                by value zero
                            length of iddata-record
     call 'CBL_THREAD_IDDATA_GET'   using iddata-ptr
                             by value 0
     set address of iddata-record to iddata-ptr
     move 'main' to iddata-name

* サブスレッドを作成します。

* 開始点
     call 'CBL_THREAD_CREATE' using   
                           'SUBPROG '  
                  by value 0   *> パラメータはありません。
                           0   *> オプション - パラメータのサイズ
                           0   *> デタッチするためのフラグ
                           0   *> デフォルトの優先順位
                           0   *> デフォルトスタック
              by reference sub-handle
     if  return-code not = 0
         display 'スレッドを作成できません。'
         stop run
     end-if

* 子スレッドが識別データを作成するまで待機します。
* その後、終了フラグを設定します。

     set sub-iddata-ptr to NULL
     perform until 1 = 0
         call 'CBL_THREAD_IDDATA_GET'  
                using    sub-iddata-ptr
                by value sub-handle
         if  sub-iddata-ptr not = null
             exit perform
         end-if
         call 'CBL_THREAD_YIELD'
     end-perform
     set address of iddata-record to sub-iddata-ptr
     move 1 to iddata-term

* 子スレッドがこのスレッドを再開するまで待機します。

     call 'CBL_THREAD_SUSPEND' using by value 0 
     display 'RTS の終了時にすべての同期が' &
             '完了します。'
     stop run.
 end program mainprog.
************************ SUBPROG.CBL **********************
 identification division.
 program-id. subprog.

 Data Division.
 Working-Storage Section.
 01 sub-iddata.
    05  sub-name             pic x(20) value 'sub'.
    05  sub-term             pic x comp-x value 0.
Local-Storage Section.
 01  iddata-ptr              usage pointer.
 01  thread-handle           usage pointer.
 01  thread-state            pic x(4) comp-x.
 01  parent-handle           usage pointer.
Linkage Section.
 01  iddata-record.
     05  iddata-name         pic x(20).
     05  iddata-term         pic x comp-x value 0.
 Procedure Division.

* 識別データを確立します。- データは初期化されます。
     call 'CBL_THREAD_IDDATA_ALLOC' using
                                  sub-iddata
                         by value length of sub-iddata

* 親スレッドを検索し、これを再開します。
*
     call 'CBL_THREAD_LIST_START' using thread-handle
                                        thread-state
                                        iddata-ptr
     set parent-handle to NULL
     perform until thread-handle = null
                or return-code not = 0
         if iddata-ptr not = null
             set address of iddata-record to iddata-ptr
             if  iddata-name = 'main'
                 set parent-handle to thread-handle
                 exit perform
             end-if
         end-if
         call 'CBL_THREAD_LIST_NEXT' using thread-handle
                                           thread-state
                                           iddata-ptr
     end-perform
     call 'CBL_THREAD_LIST_END'
     if  parent-handle = NULL
         display '同期エラー'
         stop run
     end-if
     call 'CBL_THREAD_RESUME' using by value parent-handle
     call 'CBL_THREAD_IDDATA_GET'   using iddata-ptr
                                    by value 0
     set address of iddata-record to iddata-ptr
     perform until iddata-term = 1
         call 'CBL_THREAD_YIELD'
     end-perform
     exit program.
 end program subprog.

この長めのコード例では、実際にはスレッドとアプリケーションの終了についてハンドシェイクを確立するのみです。 これについて説明する前に、主スレッドのハンドルをパラメータとして子スレッドに渡せば、このようなハンドシェイクの確立をより簡単に行えることに注目する必要があります。 この方法を使用すると、識別データに依存したり、スレッドリストをステップ実行する必要はありません。

最初に、スレッド識別データの 2 種類の作成方法に注目してください。 最初の方法は、親スレッドで、初期化済みでないデータを作成し、識別データのポインタを獲得して、割り当てられたメモリ領域を初期化します。 もう 1 つの方法は、子スレッドで、初期化済みのデータを作成し、アプリケーションによる識別データの割り当てと初期化が同時に実行される可能性を排除します。 アプリケーションで使用する方法は、予測する識別データの競合の程度によります。

また、子スレッドがスレッド識別データを作成するのを待つ親スレッドでのループと、親スレッドが終了フラグを設定するのを待つ子スレッドでのループにも注目してください。 CBL_THREAD_YIELD を呼び出すと、これらのループがハードウェアのビジー状態で待機するのを回避できますが、イベント同期オブジェクト、条件同期オブジェクト、または CBL_THREAD_SUSPEND と CBL_THREAD_RESUME を使用してコードを作成することをお奨めします。

最後に、CBL_THREAD_LIST_ API を使用している点に注目してください。 この API により、スレッドは、ランタイムシステムで認識されているすべてのスレッドをステップ実行し、スレッドハンドル、スレッド状態、識別データポインタを取得できます。 この例では、ハンドルおよび識別データポインタのみを使用しています。ただし、対象のスレッドが、デタッチされたスレッド、中断されたスレッド、または他言語のスレッドであるかどうかを呼び出しスレッドに通知することができる点で状態情報も便利です。

CBL_THREAD_LIST API が指定されていると、これを使用するスレッドは以後 CBL_THREAD_ を呼び出す場合に制約を受け、他のスレッドはすべて CBL_THREAD_ 呼び出しとその他いくつかのランタイムシステム呼び出しをまったく使用できなくなります。 この理由により、リストのステップ実行中に実行するコード量は最小限に抑え、リストがロックされている間は、ファイル入出力またはユーザ入出力を行わないことが重要です。 この制限は、CBL_THREAD_LIST_END 呼び出しによりステップ実行が終了すると、すぐに解除されます。

スレッドの中断

多くのマルチスレッドアプリケーションでは、スレッドを終了しないで、「中断」するのは珍しいことではありません。 たとえば、クライアント / サーバアーキテクチャでは、主サービススレッドが要求を見つけるために入力キューをポーリングする場合があります。要求が見つからない場合は、再度入力キューをポーリングする前に CPU を別のプロセスまたはスレッドに使用させます。 これは、CBL_THREAD_YIELD を呼び出すことで簡単に行うことができます。CBL_THREAD_YIELD を呼び出すと、アプリケーションの別のスレッド (オペレーティングシステムによっては、別のプロセスの別のスレッド) に CPU を譲ります。

他に、スレッドが CPU の所有権を無期限に放棄し、何らかのイベントが確実に発生した場合のみに CPU を使用できます。 CBL_THREAD_SUSPEND により、中断されたスレッドのスレッドハンドルを使用して別のスレッドが CBL_THREAD_RESUME を呼び出すまで呼び出し側スレッドを中断できます。 スレッドは対象となるスレッドがそれ自身を中断する前に、CBL_THREAD_RESUME を 1 回以上呼び出せます。この場合には、CBL_THREAD_SUSPEND の呼び出しは呼び出し側スレッドにすぐに戻り、CPU の所有権を放棄しません。 この操作は、セマフォを数えるのに非常に似ています。作成者 / 使用者間の問題は、CBL_THREAD_SUSPEND と CBL_THREAD_RESUME ルーチンのみを使用すると解決します。

スレッドの識別

スレッドをアプリケーション内で作成された他のスレッドと区別すると役立つことがあります。 たとえば、4 つのスレッドをもつアプリケーションで 2 つのスレッドが作成側と使用側の関係をなす場合には、お互いのスレッドのスレッドハンドルを知ることはこれら連動する 2 つのスレッドにとって役に立ちます。 これらのハンドルを取得した後は、CBL_THREAD_SUSPEND と CBL_THREAD_RESUME の呼び出しのみを使用してすべての同期を取ることができます。 アプリケーション内の各スレッドがそれぞれの名前を作成し (かつ、名前がその機能に関連する)、その名前をスレッドハンドルに関連付けると、連動するスレッドはスレッド名のリストを検索し、お互いのハンドルを検出できます。

グローバルにアクセス可能なデータを各スレッドとそのハンドルに関連付けると、終了フラグを保持し、CBL_THREAD_KILL を使用する可能性を未然に防ぐことができます。 アプリケーションの各スレッドは、終了要求を確認するためにその終了フラグをポーリングできます。 終了するスレッドは、正常に終了する前に、ロックが保持されていないことと、アクションの同期を取る必要がないことを確認します。

スレッドのグローバルにアクセス可能なデータは、スレッド内で CBL_THREAD_IDDATA_ALLOC ルーチンを実行するとスレッドハンドルに関連付けられます。 スレッドハンドルがすでに認識されている場合は、CBL_THREAD_IDDATA_GET を呼び出してデータを取得します。スレッドハンドルが認識されていない場合は、n ルーチンを使用してこのデータを取得します。

例 - グローバルにアクセス可能なデータとスレッドハンドルの関連付け

次の例では、スレッド内で CBL_THREAD_IDDATA_ALLOC を呼び出して、グローバルにアクセス可能なデータをスレッドハンドルに関連付ける方法を示します。 スレッドハンドルがすでに認識されている場合は、CBL_THREAD_IDDATA_GET を呼び出してデータを取得します。スレッドハンドルが認識されていない場合は、CBL_THREAD_LIST_n ルーチンを使用してこのデータを取得します。

********************* MAINPROG.CBL ********************
 identification division.
 program-id. mainprog.

 Data Division.
 Local-Storage Section.
 01 iddata-ptr       usage pointer.
 01 sub-iddata-ptr   usage pointer. 
 01 sub-handle       usage thread-pointer.
 Linkage Section.
  01  iddata-record.
     05 iddata-name  pic x(20).
   05 iddata-term    pic x comp-x value 0.

 Procedure Division.

* 識別データを確立します。- 識別データの割り当て時に 
* データは初期化されません。
* ポインタの取得後に初期化を行います。

     call 'CBL_THREAD_IDDATA_ALLOC' using
                                    by value zero
                                    length of iddata-record
     call 'CBL_THREAD_IDDATA_GET'   using iddata-ptr
                                    by value 0
     set address of iddata-record   to iddata-ptr
     move 'main' to iddata-name

* サブスレッドを作成します。

     start 'SUBPROG '  identified by sub-handle

* 子スレッドが識別データを作成するまで待機します。
* その後、終了フラグを設定します。

     set sub-iddata-ptr to NULL
     perform until 1 = 0
         call 'CBL_THREAD_IDDATA_GET' using sub-iddata-ptr
                                      by value sub-handle
         if  sub-iddata-ptr not = null
             exit perform
         end-if
         call 'CBL_THREAD_YIELD'
     end-perform
     set address of iddata-record to sub-iddata-ptr
     move 1 to iddata-term

*> 子スレッドがこのスレッドを再開するまで待機します。

     call 'CBL_THREAD_SUSPEND' using by value 0 
     display 'RTS の終了時にすべての同期が' &
             '完了します。'
     wait for sub-handle   *> スレッドのリソースを解放します。
     stop run.
 end program mainprog.
*************************** SUBPROG.CBL ****************************
 identification division.
 program-id. subprog.

 Data Division.
 Working-Storage Section.
 01 sub-iddata.
    05 sub-name	          pic x(20) value 'sub'.
    05 sub-term	          pic x comp-x value 0.
 Local-Storage Section.
 01 iddata-ptr            usage pointer.
 01 thread-handle         usage pointer.
 01 thread-state          pic x(4) comp-x.
 01 parent-handle         usage pointer.
 Linkage Section.
 01  iddata-record.
    05 iddata-name        pic x(20).
    05 iddata-term        pic x comp-x value 0.
Procedure Division.

* 識別データを確立します。- データは
* 初期化されます。
     call 'CBL_THREAD_IDDATA_ALLOC'
                          using sub-iddata
                          by value length of sub-iddata

* 親スレッドを検索し、これを再開します。

     call 'CBL_THREAD_LIST_START'		
                       using thread-handle
                             thread-state
                             iddata-ptr
     set parent-handle to NULL
     perform until thread-handle = null
                or return-code not = 0
         if iddata-ptr not = null
             set address of iddata-record   to iddata-ptr
             if  iddata-name = 'main'
                 set parent-handle to thread-handle
                 exit perform
            end-if
         end-if
         call 'CBL_THREAD_LIST_NEXT' using thread-handle
                                           thread-state
                                           iddata-ptr
     end-perform
     call 'CBL_THREAD_LIST_END'
     if  parent-handle = NULL
         display '同期エラー'
         stop run
     end-if
     call 'CBL_THREAD_RESUME' using by value parent-handle
     call 'CBL_THREAD_IDDATA_GET' using iddata-ptr
                                  by value 0
     set address of iddata-record   to iddata-ptr
     perform until iddata-term = 1
         call 'CBL_THREAD_YIELD'
     end-perform
     exit program.
 end program subprog. 

この例では、スレッドとアプリケーションを終了するためのハンドシェイクを確立します。 このようなハンドシェイクは、主スレッドのハンドルをパラメータとして子スレッドに渡すことでより簡単に行えることに注目してください。 この方法を使用すると、識別データに依存したり、スレッドリストをステップ実行する必要はありません。

この例では、スレッドの識別データを作成する 2 種類の方法を示しています。 最初の方法は、親スレッドで、初期化済みでないデータを作成し、識別データのポインタを獲得して、割り当てられたメモリ領域を初期化します。 もう 1 つの方法は、子スレッドで、初期化されたデータを作成し、アプリケーションによる識別データの割り当てと初期化が同時に実行される可能性を排除します。 アプリケーションで使用する方法は、予測する識別データの競合の程度によります。

また、子スレッドがスレッド識別データを作成するのを待つ親スレッドでのループと、親スレッドが終了フラグを設定するのを待つ子スレッドでのループにも注目してください。 CBL_THREAD_YIELD を呼び出すと、これらのループがハードウェアのビジー状態で待機するのを回避できますが、イベント同期オブジェクト、条件同期オブジェクト、または CBL_THREAD_SUSPEND と CBL_THREAD_RESUME を使用してコードを作成することをお奨めします。

最後に、CBL_THREAD_LIST_n ルーチンを使用している点に注目してください。 これらのルーチンにより、スレッドは、ランタイムシステムで認識されているすべてのスレッドをステップ実行し、スレッドハンドル、スレッド状態、識別データポインタを取得できます。 この例では、ハンドルおよび識別データポインタのみを使用しています。ただし、対象のスレッドが、デタッチされたスレッド、中断されたスレッド、または他言語のスレッドであるかどうかを呼び出しスレッドに通知することができる点で状態情報も便利です。

CBL_THREAD_LIST_n ルーチンが指定されていると、これを使用するスレッドは以後 CBL_THREAD_n を呼び出す場合に制約を受け、他のスレッドはすべて CBL_THREAD_n 呼び出しとその他いくつかのランタイムシステム呼び出しをまったく使用できなくなります。 この理由により、リストのステップ実行中に実行するコード量は最小限に抑え、リストがロックされている間は、ファイル入出力またはユーザ入出力を行わないことが重要です。 この制限は、CBL_THREAD_LIST_END 呼び出しによりステップ実行が終了すると、すぐに解除されます。

他言語のスレッド

複数言語を使用する環境では、オペレーティングシステムの機能によって直接作成されたスレッドと、START 文または CBL_THREAD_CREATE ルーチン以外で作成されたスレッドに対してはいくつかの制限と要件があります。


注:ランタイムシステムがサポートするすべてのマルチスレッド機能は、他言語スレッドで作成されたハンドルを検出し、使用状況が無効な場合に報告します。 ただし、(上記で説明するとおり) COBOL またはランタイムシステムコードを実行したスレッドが終了する前にこのスレッド対するランタイムシステムのリソースを解放するかどうかは完全にアプリケーションに依存します。


呼び出されるプログラムの取り消し

CANCEL 操作では、操作の完了時間が制限されるため、CANCEL 操作が終了する前に取り消されたモジュールがスレッドで実行される可能性があります。 このため、マルチスレッドアプリケーションでは、CANCEL 文を使用しないことをお奨めします。

最適化とプログラミングのヒント

最適化された効率的なプログラムを作成するには、次のことに注意してください。

マルチスレッドアプリケーションで使用できるコンパイラ指令は、マルチスレッドアプリケーションおよびシングルスレッドアプリケーションの性能に影響します。