排他制御

排他制御は、コード領域を同時に実行できるスレッドの数を制御します。インテル® スレッディング・ビルディング・ブロック (インテル® 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 オブジェクトの破棄はロックを解放します。これは、AllocateNodeacquire メソッドおよび release メソッドを使用するバージョンでも同じです。明示的な解放によりロックが早めに解放され、デストラクターはロックが解放されたと判断して何も行いません。

インテル® TBB のすべての mutex では同様のインターフェイスが使用されているため、簡単に習得できるだけでなく、一般的なプログラミングが可能です。例えば、すべての mutex には入れ子の scoped_lock 型があり、M 型の mutex を指定すると、対応するロックの型は M::scoped_lock になります。

ヒント

前のサンプルで示したように、mutex の型には typedef を使用することを推奨します。この方法では、ほかのコードを編集することなく、容易にロックの型を変更できます。サンプルでは、typedeftypedef queuing_mutex FreeListMutexType に置換することもできます。置換後もコードは同じ動作をします。