排他制御#
排他制御は、コード領域を同時に実行するスレッドの数を制御します。oneAPI スレッディング・ビルディング・ブロック (oneTBB) では、相互排他はミューテックスとロックによって実装されます。ミューテックスは、スレッドがロックを取得できるオブジェクトです。一度に1つのスレッドのみミューテックスでロックを取得できます。他のスレッドは、自分の順番になるまで待機しなければなりません。
spin_mutex は、最も単純なミューテックスです。spin_mutex のロックを取得しようとするスレッドは、ロックが取得できるまで待機します。spin_mutex は、少数の命令でロックが保持される場合にのみ適しています。例えば、次のコードは、FreeListMutex ミューテックスを使用して共有変数 FreeList を保護します。一度に 1 つのスレッドだけが FreeList にアクセスできることを確認します。
Node* FreeList;
typedef spin_mutex FreeListMutexType;
FreeListMutexType FreeListMutex;
Node* AllocateNode() {
Node* n;
{
FreeListMutexType::scoped_lock lock(FreeListMutex);
n = FreeList;
if( n )
FreeList = n->next;
}
if( !n )
n = new Node();
return n;
}
void FreeNode( Node* n ) {
FreeListMutexType::scoped_lock lock(FreeListMutex);
n->next = FreeList;
FreeList = n;
}scoped_lock のコンストラクターは、FreeListMutex にほかのロックがなくなるまで待機します。デストラクターはロックを解放します。AllocateNode ルーチン内の中括弧は異常に見えるかもしれません。その役割は、他のスレッドができるだけ早くロックが得られるよう、ロック期間を短く保つことです。
警告
ロック・オブジェクトには必ず名前を付けてください。そうしないと、すぐに破棄されてしまいます。例えば、例中の scoped_lock オブジェクトの作成を
FreeListMutexType::scoped_lock (FreeListMutex);に変更すると、実行がセミコロンに達したときに scoped_lock が破棄され、FreeList にアクセスされる前にロックが解放されます。
AllocateNode を記述する別の方法を以下に示します。
Node* AllocateNode() {
Node* n;
FreeListMutexType::scoped_lock lock;
lock.acquire(FreeListMutex);
n = FreeList;
if( n )
FreeList = n->next;
lock.release();
if( !n )
n = new Node();
return n;
}acquire メソッドは、ミューテックスのロックを取得できるまで待機し、release メソッドはロックを解放します。
ロックによって保護されるコードの範囲が容易に判断できるように、可能な場所に中括弧を挿入することを推奨します。
C 言語のロック・インターフェイスに詳しいプログラマーは、なぜミューテックス・オブジェクトを単純に acquire (取得)/ release (解放) にしないのか疑問に思われるかもしれません。この理由は、保護領域から例外がスローされた場合、コントロールは解放をスキップするため、C インターフェイスは例外セーフではないためです。オブジェクト指向のインターフェイスでは、保護領域が通常のコントロール・フローで終了したか、例外をスローしたかに関係なく、scoped_lock オブジェクトの破棄はロックを解放します。これは、acquire メソッドと release メソッドを使用した AllocateNode のバージョンにも当てはまります。明示的な release によってロックが事前に解放され、デストラクターはロックが解放されたことを認識して何も行いません。
oneTBB のすべてのミューテックスでは、同様のインターフェイスが使用されているため、容易に理解できるだけでなく、一般的なプログラミングができます。例えば、すべてのミューテックスには入れ子の scoped_lock タイプがあり、M タイプのミューテックスを指定すると、対応するロックのタイプは M::scoped_lock になります。
ヒント
前の例で示すように、ミューテックスのタイプには typedef を使用することを推奨します。この方法では、他のコードを編集することなく、容易にロックの型を変更できます。この例では、typedef を typedef queuing_mutex FreeListMutexType に置き換えても、コードは正しく動作します。