この章では, 共有データの整合性を維持するのに必要な同期メカニズムについて説明します。 たとえば,ある種のVAX命令で保証されている不可分性などについて説明します。
アプリケーションで複数の実行スレッドを使用しており, これらのスレッドが同じデータをアクセスする場合には, Alphaシステムで共有データの整合性を保護するために, アプリケーションに明示的な同期メカニズムを追加しなければなりません。 正しく同期をとらなかった場合には, 1つのアプリケーション・スレッドによって開始されたデータ・アクセスが, 別のスレッドによって同時に開始されたアクセスを妨害する可能性があり, その結果,データは予測できない状態になる可能性があります。
VAXシステムでは,必要とされる同期のレベルは実行スレッドの関係に応じて異なります。 次に示すいくつかの場合は,同期について考慮しなければなりません。
ASTスレッドはアプリケーションによって開始されますが, オペレーティング・システムによって開始されることもあります。たとえば, オペレーティング・システムは ASTを使用して状態を入出力状態ブロックに書き込みます。また, オペレーティング・システムではASTを使用して, 指定したユーザ・バッファへのバッファを介した読み込み操作を終了します。
VAXシステムでは, マルチプロセッサ・システムの並列処理機能を利用するアプリケーションは常に, ロックやセマフォ, インターロック命令などの明示的な同期メカニズムを準備することにより, 共有データを保護しなければなりません。しかし, ユニプロセッサ・システムで複数のスレッドを使用するアプリケーションは, 明示的に共有データを保護しない可能性があります。これらのアプリケーションは, VAXユニプロセッサ・システムで実行されるアプリケーションのスレッド間の同期を保証する VAXアーキテクチャの機能によって提供される, 暗黙の保護に依存している可能性があります (第6.1.1項を参照)。
たとえば,複数のスレッドが重要なコード領域をアクセスするときに, アクセスの同期をとるためにセマフォ変数を使用するアプリケーションは, 不可分な操作によってインクリメントされるセマフォを必要とします。 VAXシステムでは,このような不可分な操作はVAXアーキテクチャによって保証されています。
Alphaアーキテクチャでは, VAXアーキテクチャと同じように同期がとられるという保証はありません。 Alphaシステムでは,このセマフォへのアクセスや, 複数の実行スレッドがアクセスできるデータへのアクセスは, 明示的に同期をとらなければなりません。 VAXシステムの場合と同じ保護を実現するために使用できる Alphaアーキテクチャの機能については, 第6.1.2項を参照してください。
VAXアーキテクチャの次の機能は, ユニプロセッサ・システムで実行される複数の実行スレッド間で同期を保証します (ただしVAXアーキテクチャは, マルチプロセッサ・システムに対してはこのような不可分性を保証していません)。
たとえば,VAX Increment Long (INCL) 命令は指定されたロングワードの内容をフェッチし,その値をインクリメントし, 値を元のロングワードに格納します。 これらの操作は割り込み不可能な方法で実行されます。しかし,Alphaシステムでは, 各ステップを別々の命令で実行しなければなりません。
VAXシステムとの互換性を維持するために,Alphaアーキテクチャでは,リード/ライト (読み込み/書き込み)操作が不可分な方法で実行されることを保証する, 1組の命令を定義しています。これらの命令についての説明と, 高級言語で作成されたプログラムでこの機能を使用した場合に, Alphaシステムのコンパイラがどのような操作を実行するかについては, 第6.1.2項を参照してください。
しかし,VAXシステムでも,
VAX命令の不可分性に暗黙に依存することは望ましくありません。
VAXシステムのコンパイラは,インクリメント操作(x = x + 1
)
のような不可分な命令が記述されている場合でも,
最適化のためにこのような命令を実現しない可能性があります。
このようにメモリ・アクセスの粒度が変更された結果, どのタイプのデータを共有するかについても考慮しなければなりません。 VAXシステムでは, 共有されるバイト・サイズまたはワード・サイズのデータは個別に操作できます。 Alphaシステムでは,バイト・サイズまたはワード・ サイズの項目を含むロングワードまたはクォドワード全体を操作しなければなりません。 したがって,明示的に共有されるデータに隣接しているという理由だけで, 隣接データも暗黙のうちに共有されることになります。
コンパイラは第6.1.2項で説明するAlpha命令を使用して, バイト・サイズおよびワード・サイズのデータの整合性を保証します。
VAXシステムとの互換性を維持するために,Alphaアーキテクチャでは, システム内のすべてのプロセッサから見て, 読み込み/書き込み操作が指定した順に実行されるようにする命令をサポートします。 この命令についての説明と, 高級言語でこの命令をどのように使用するかについての説明は, 第6.1.2項を参照してください。この同期をとるために Alphaアーキテクチャが提供する機能についての説明と, 高級言語プログラムでこの機能を利用する際に, Alphaシステムのコンパイラがどのような操作を実行するかについての説明は, 第6.3節を参照してください。
VAXアーキテクチャの不可分な機能との互換性を維持するために, Alphaアーキテクチャでは2つのメカニズムを定義しています。
Load-locked/Store-conditional命令を使用することにより, Alphaシステムのコンパイラはバイト・サイズおよびワード・ サイズのデータに対して不可分なアクセスを実現できます。さらに, Alphaシステムのコンパイラでは,volatile 属性によって宣言されたバイト・サイズおよびワード・サイズのデータをアクセスするときに, Load-locked/Store-conditional命令を生成できます(Alphaアーキテクチャでは, ロングワード・サイズとクォドワード・サイズのデータの不可分なロード/ ストア操作は準備されています)。
アプリケーションで同期が保証されると仮定している部分を検出するための 1つの方法として,複数の実行スレッド間で共有されるデータを識別し, 各スレッドからのデータ・アクセスを確認する方法があります。 共有データを検出する場合には,意図的に共有されるデータだけでなく, 暗黙のうちに共有されるデータも検出しなければなりません。 暗黙のうちに共有されるデータとは, 複数の実行スレッドによってアクセスされるデータに近接しているために共有されるデータです。 たとえば,$QIO,$ENQ, $GETJPIなどのシステム・サービスの結果としてオペレーティング・システムが生成した ASTによって書き込まれるデータは,このような暗黙のうちに共有されるデータです。
Alphaシステムのコンパイラはある状況では, 省略時の設定でクォドワード命令を使用するため, 共有データが格納されているクォドワードと同じクォドワード内のすべてのデータは暗黙のうちに共有される可能性があります。 たとえば,コンパイラは自然な境界にアラインされていないデータをアクセスするためにクォドワード命令を使用します (アドレスがデータ・サイズで割り切れる場合には, データは自然にアラインされています。詳しくは 第7章を参照してください。 コンパイラは省略時の設定により,宣言されたデータを自然な境界にアラインします)。
データ・アクセスを調べる場合には, 別のスレッドが処理中の状態のデータを確認する可能性がないかどうかを判断し, このような可能性がある場合には, それがアプリケーションにとって重要な問題であるかどうかを判断してください。 場合によっては, 共有データの値が正確であることがそれほど重要でない場合もあります。たとえば, アプリケーションが変数の相対値だけを必要とする場合には, 正確な値は必要ありません。これらを調べるために,次の事項をチェックしてください。
例 6-1のプログラムは, VAXアプリケーションで不可分性が保証されると仮定した部分を簡単に示しています。 このプログラムでは,flag という変数を使用しており, ASTスレッドはこの変数を通じてメイン処理スレッドと通信します。この例では, カウンタ変数が前もって定義した値に到達するまで, メイン処理ループは処理を継続します。プログラムは flag を最大値に設定するAST割り込みをキューに登録し, 処理ループを終了します。
#include <ssdef.h> #include <descrip.h> #define MAX_FLAG_VAL 1500 int ast_rout(); long time_val[2]; short int flag; /* accessed by main and AST threads */ main( ) { int status = 0; static $DESCRIPTOR(time_desc, "0 ::1"); /* changes ASCII time value to binary value */ status = SYS$BINTIM(&time_desc, &time_val); if ( status != SS$_NORMAL ) { printf("bintim failure\n"); exit( status ); } /* Set timer, queue ast */ status = SYS$SETIMR( 0, &time_val, ast_rout, 0, 0 ); if ( status != SS$_NORMAL ) { printf("setimr failure\n"); exit( status ); } flag = 0; /* loop until flag = MAX_FLAG_VAL */ while( flag < MAX_FLAG_VAL ) { printf("main thread processing (flag = %d)\n",flag); flag++; } printf("Done\n"); } ast_rout() /* sets flag to maximum value to stop processing */ { flag = MAX_FLAG_VAL; }例 6-1では,flag という名前の変数がメイン実行スレッドとASTスレッドの間で明示的に共有されます。 このプログラムでは, この変数の整合性を保護するために同期メカニズムを使用していません。つまり, インクリメント操作が不可分な方法で実行されることを暗黙のうちに仮定しています。
Alphaシステムでは,このプログラムは常に VAXシステムと同じように動作するわけではありません。これは, 図 6-2に示すように,新しい値をメモリに格納する前に, メイン実行スレッドがインクリメント操作の途中で ASTスレッドによって割り込まれる可能性があるからです(実際のアプリケーションでは, 多くのASTスレッドによって割り込みが発生する可能性がもっと高くなります)。 この例では, ASTスレッドはインクリメント操作が終了する前にこの操作に割り込みをかけ, 変数の値を最大値に設定します。しかし,制御がメイン・スレッドに戻された後, インクリメント操作は終了し,ASTスレッドの値が上書きされます。 ループ・テストを実行すると,値は最大値でないため,処理ループは継続されます。
このような不可分性への依存を修正するには,次の処理を実行してください。
共有変数を不可分性に関する組み込み機能によって保護するには, これらの変数はアラインされたロングワードまたはアラインされたクォドワードでなければなりません。
#include <ssdef.h> #include <descrip.h> #include <builtins.h> 【1】 #define MAX_FLAG_VAL 1500 int ast_rout(); long time_val[2]; int 【2】 flag; /* accessed by mainline and AST threads */ main( ) { int status = 0; static $DESCRIPTOR(time_desc, "0 ::1"); /* changes ASCII time value to binary value */ status = SYS$BINTIM(&time_desc, &time_val); if ( status != SS$_NORMAL ) { printf("bintim failure\n"); exit( status ); } /* Set timer, queue ast */ status = SYS$SETIMR( 0, &time_val, ast_rout, 0, 0 ); if ( status != SS$_NORMAL ) { printf("setimr failure\n"); exit( status ); } flag = 0; while( flag < MAX_FLAG_VAL ) /* perform work until flag set to zero */ { printf("mainline thread processing (flag = %d)\n",flag); __ADD_ATOMIC_LONG(&flag,1,0); 【3】 } printf("Done\n"); } ast_rout() /* sets flag to maximum value to stop processing */ { flag = MAX_FLAG_VAL; }次のリストの各項目は 例 6-2に示した番号に対応しています。
【1】 | DEC C for OpenVMS Alphaシステムの不可分性に関する組み込み機能を使用するには, builtins.hヘッダ・ファイルをインクルードしなければなりません。 |
【2】 | このバージョンでは,変数 flag はロングワードとして宣言されているため, 不可分なアクセスが可能です(不可分性に関する組み込み機能を使用するには, 変数をこのように宣言しなければなりません)。 |
【3】 | インクリメント操作は不可分性に関する組み込み機能を使用して実行されます。 |
例 6-1では, 2つのスレッドはどちらも同じ変数をアクセスします。しかし,Alphaシステムでは, 暗黙のうちに共有される変数に対してアプリケーションで不可分性を持たせることが可能です。 この例では,2つの変数はロングワードまたはクォドワードの境界内で物理的に隣接しています。 VAXシステムでは,各変数は個別に処理できます。 Alphaシステムでは,ロングワード・データとクォドワード・データのみ, 不可分な読み込み操作と書き込み操作がサポートされるため, 処理の対象となるバイトを変更する前に, ロングワード全体をフェッチしなければなりません (データ・アクセス粒度の変更についての詳しい説明は, 第7章を参照してください)。
この問題を示すために,例 6-1 のプログラムを変更したバージョンについて考えてみましょう。 このバージョンでは,メイン・スレッドと ASTスレッドはそれぞれデータ構造体で宣言された別々のカウンタ変数をインクリメントします。 カウンタ変数は次の文によって宣言されます。
struct { short int flag; short int ast_flag; };メイン・スレッドとASTスレッドがどちらも, 処理の対象となるワードを同時に変更しようとした場合には, 2つの操作が実行されるタイミングに応じて,結果は予想できなくなります。
同期に関するこの問題を解決するには,次の処理を実行してください。
データ構造の各要素が自然なクォドワード境界に強制的にアラインされるように, データの間にバイトを挿入することもできます。 OpenVMS Alphaコンパイラは省略時の設定により, データを自然な境界にアラインします。
たとえば,他の実行スレッドからの妨害を受けずに,
データ構造の各フラグ変数を確実に変更できるようにするには,
64ビットの値となるように変数の宣言を変更します。
DEC Cを使えば,double
データ型を使用できます。
次の例を参照してください。
struct { double flag; double ast_flag; };
VAXマルチプロセシング・システムは従来,マルチプロセシング・システム内の 1つのプロセッサが複数のデータを書き込むときに, これらのデータが書き込まれた順序と同じ順序で, 他のすべてのプロセッサから確認できるように設計されていました。たとえば, CPU Aがデータ・バッファを書き込み(図 6-3で Xによって表現されるもの),その後でフラグを書き込んだ場合 (図 6-3でYによって表現されるもの), CPU Bはフラグの値を確認することにより, データ・バッファが変更されたことを判断できます。
Alphaシステムでは,メモリ・サブシステム全体の性能を向上するために, メモリとの間の読み込み操作および書き込み操作の順序が変更される可能性があります。 単一プロセッサで実行されるプロセスの場合には, そのプロセッサからの書き込み操作は要求された順に読み込み可能になることを仮定できます。 しかし,マルチプロセッサ・アプリケーションの場合には, メモリに対する書き込み操作の結果がシステム全体から確認できるようになる順序を, 前もって判断できません。つまり,CPU Aによって実行される書き込み操作は, 実際に書き込まれた順序とは異なる順序でCPU Bから見える場合もあります。
図 6-3はこの問題を示しています。CPU Aは Xに対する書き込み操作を要求し,その後,Yに対する書き込み操作を要求します。 CPU BはYからの読み込み操作を要求し,Yの新しい値を確認し, Xの読み込み操作を開始します。Xの新しい値がまだメモリに書き込まれていない場合には, CPU Bは前の値を読み込みます。この結果,CPU AとCPU Bで実行され るプロシージャが依存するトークン受け渡しプロトコルは正しく機能しなくなります。 CPU Aはデータを書き込み,フラグ・ビットをセットできますが,CPU Bは, データが実際に書き込まれる前にフラグ・ビットがセットされていることを確認する可能性があり, その結果,誤ったメモリの内容を使用してしまいます。
並列に実行され,読み込み/書き込みの順序に依存するプログラムは, Alphaシステムで正しく実行するために何らかの設計変更が必要です。 アプリケーションに応じて,次の方法を使用してください。
VESTコマンドの/PRESERVE修飾子は, VAXシステムで提供されるのと同じ不可分性を保証して, Alphaシステムでトランスレートされた VAXイメージを実行できるようにするためのキーワードを受け付けます。 /PRESERVE修飾子のキーワードは複数のタイプの不可分性保護機能を提供します。 ただし,これらの/PRESERVE修飾子のキーワードを指定すると, アプリケーションの性能が低下する可能性があります (/PRESERVE修飾子の指定についての詳しい説明は, 『DECmigrate for OpenVMS AXP Systems Translating Images』を参照してください)。
VAXシステムでVAX命令が不可分な方法で実行できる操作を, トランスレートされたイメージでもできるようにするには,/PRESERVE修飾子に対して INSTRUCTION_ATOMICITYキーワードを指定します。
ロングワードまたはクォドワードに格納された隣接バイトを同時に更新し, これらの各バイトが相互に妨害しないようにするには,/PRESERVE修飾子に対して MEMORY_ATOMICITYキーワードを指定します。
読み込み/書き込み操作が実行される順序が要求した順序と同じ順序で実行されるように見えるようにするには, /PRESERVE修飾子に対してREAD_WRITE_ORDERINGキーワードを指定します。