排他制御

排他制御#

排他制御は、コード領域を同時に実行するスレッドの数を制御します。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 を使用することを推奨します。この方法では、他のコードを編集することなく、容易にロックの型を変更できます。この例では、typedeftypedef queuing_mutex FreeListMutexType に置き換えても、コードは正しく動作します。