マルチスレッドのプログラミング

共有メモリー型のマルチプロセッサ

例 9–5 は、「生産者 / 消費者」問題の代表的な解決方法です。

このプログラムは、現在の SPARC ベースのマルチプロセッサでは正しく動作しますが、すべてのマルチプロセッサが強く順序付けられたメモリーをもつことを想定しています。したがって、このプログラムには移植性がありません。


例 9–5 「生産者 / 消費者」問題 — 共有メモリー型のマルチプロセッサ

char buffer[BSIZE];
unsigned int in = 0;
unsigned int out = 0; 
 
/* Thread 1 (producer) */
/* Thread 2 (consumer) */
void
producer(char item) {
     do
     {
        ;/* nothing */
     }
     while
         ((in - out) == BSIZE);

     buffer[in%BSIZE] = item;
     in++;
}
char
consumer(void) {
     char item;
     do
     {
        ;/* nothing */
     }
     while
         ((in - out) == 0);
     item = buffer[out%BSIZE];
     out++;
}


このプログラムは、生産者と消費者がそれぞれ 1 つしか存在せず、かつ共有メモリー型のマルチプロセッサ上で動作するときは正しく動作します。inout の差が、バッファー内のデータ数となります。

生産者はバッファーに新しい項目のための空きができるまで、この差を繰り返し計算しながら待ちます。消費者は、バッファーに項目が入れられるのを待ちます。

強く順序付けられたメモリーでは、一方のプロセッサへの変更が、もう一方のプロセッサのメモリーにただちに伝えられます。強く順序付けられたメモリーでは、inout が最終的にオーバーフローしても、このソリューションが成立します。このオーバーフローは、BSIZE が 1 ワードで表現できる最大の整数より小さい場合に発生します。

共有メモリー型のプロセッサは、必ずしも強く順序付けられたメモリーをもつ必要はありません。つまり、あるプロセッサによるメモリーへの変更が、ほかのプロセッサにただちに伝わるとは限りません。あるプロセッサによって、異なったメモリー領域 2 箇所に変更が加えられたとき、どうなるか考えてみましょう。もう一方のプロセッサには、メモリーの変更がただちに伝わらないので、このプロセッサから検出できる変更の順序は最初の順序と同じであるとは限りません。

変更内容は、まず「ストアバッファー」に入れられます。このストアバッファーは、キャッシュからは参照できません。

プロセッサは、プログラムの整合性を保証するために、これらのストアバッファーをチェックします。しかし、ほかのプロセッサから、このストアバッファーは参照できません。このため、あるプロセッサが書き込んだ内容は、キャッシュに書き込まれるまで、ほかのプロセッサから参照できません。

同期プリミティブは、特別な命令でストアバッファーの内容をキャッシュにフラッシュしています。第 4 章同期オブジェクトを使ったプログラミングを参照してください。したがって、共有データをロックで保護すればメモリーの整合性が保証されます。

メモリーの順序付けが非常に弱い場合は例 9–5 で問題が発生します。消費者は、対応するバッファースロットへの変更を知る前に、生産者によって in が 1 つ増やされたことを知る場合があります。

あるプロセッサのストア順序が、別のプロセッサからは違った順序で見えることがあるため、このメモリーの順序付けを「弱い順序付け」と呼びます。ただし、同じプロセッサから見たメモリーは常に整合性を保っています。この問題を解決するには、相互排他ロックを使用して、ストアバッファーの内容をキャッシュにフラッシュしなければなりません。

最近は、メモリーの順序付けが弱くされる傾向にあります。このため、プログラマは大域データや共有データをロックで保護するよう注意する必要があります。

例 9–5例 9–6 に示すように、ロックは重要です。