排他制御は、コード領域を同時に実行できるスレッドの数を制御します。インテル® スレッディング・ビルディング・ブロック (インテル® TBB) では、排他制御は mutex とロック によって実装されています。mutex は、スレッドがロックを取得できるオブジェクトです。一度に 1 つのスレッドのみ mutex でロックを取得できます。ほかのスレッドは、自分の順番になるまで待たなければなりません。
最も簡単な mutex は、spin_mutex です。spin_mutex でロックを取得しようとするスレッドは、ロックを取得するまで、ビジーウェイトを行います。spin_mutex は、少数の命令をロックする場合に適しています。例えば、次のコードは、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 メソッドは mutex でロックを取得するまで待ち、release メソッドはロックを解放します。
ロックによって保護されるコード範囲が容易に判断できるように、可能な場所に中括弧を挿入することを推奨します。
C 言語のロック・インターフェイスに詳しいプログラマーは、なぜ mutex オブジェクトに acquire メソッドや release メソッドがないのか疑問に思われるかもしれません。その理由は、保護領域から例外がスローされた場合、コントロールは解放をスキップするため、C インターフェイスは例外セーフではないためです。オブジェクト指向のインターフェイスでは、保護領域が通常のコントロール・フローで終了したか例外をスローしたかに関係なく、scoped_lock オブジェクトの破棄はロックを解放します。これは、AllocateNode の acquire メソッドおよび release メソッドを使用するバージョンでも同じです。明示的な解放によりロックが早めに解放され、デストラクターはロックが解放されたと判断して何も行いません。
インテル® TBB のすべての mutex では同様のインターフェイスが使用されているため、簡単に習得できるだけでなく、一般的なプログラミングが可能です。例えば、すべての mutex には入れ子の scoped_lock 型があり、M 型の mutex を指定すると、対応するロックの型は M::scoped_lock になります。
前のサンプルで示したように、mutex の型には typedef を使用することを推奨します。この方法では、ほかのコードを編集することなく、容易にロックの型を変更できます。サンプルでは、typedef を typedef queuing_mutex FreeListMutexType に置換することもできます。置換後もコードは同じ動作をします。