mutex に精通すると、さまざまな mutex 属性を使い分けることができます。mutex 属性は、汎用性と効率性のトレードオフに関わるため、いくつか知っておくと便利です。適切な属性を選択することにより、パフォーマンスの向上に役立ちます。mutex の特性を次に説明します。下記の表の要約も参照してください。
スケーラブル。一部の mutex は、スケーラブル と呼ばれます。mutex は実行を一度に 1 つのスレッドに制限するため、この名称は厳密には正しくありません。スケーラブルな mutex は、この制限以下 のことは行いません。待機スレッドがプロセッサー・サイクルとメモリー帯域幅を過剰に消費し、実際のワークを行うスレッドの速度を減少させてしまう場合、mutex はシリアル実行よりもパフォーマンスを低下させることがあります。スケーラブル mutex は、競合が少ない場合はスケーラブルでない mutex よりも低速なことがあるため、このような場合はスケーラブルでない mutex を使用する方が良いでしょう。確実でない場合は、スケーラブルな mutex を使用してください。
フェア。mutex は フェア (公正) またはアンフェア (非公正) です。フェアな mutex は、要求された順番にスレッドを処理し、スレッドが放置されることを回避します。各スレッドで、それぞれ順番が回ってきます。しかし、アンフェアな mutex の方が、割り込みのためにスリープしている次のスレッドの代わりに実行中のスレッドを先に処理するため、フェアな mutex よりも高速な場合があります。
再帰。mutex は、再帰 または非再帰 です。再帰 mutex は、すでにロックを保持しているスレッドが別の mutex のロックを取得することを許可します。これは、一部の再帰アルゴリズムでは役立つものの、通常はロック実装のオーバーヘッドが増えます。
イールドまたはブロック。実装に依存してパフォーマンスに影響します。長い待機では、インテル® スレッディング・ビルディング・ブロック (インテル® TBB) の mutex はイールド またはブロック のいずれかです。ここでは、イールド とは、進行可能かどうか繰り返しポーリングし、進行できない場合はプロセッサーを「一時的に」イールド[5] する (ほかのスレッドに譲る) ことを意味します。ブロック とは、mutex が進行を許可するまでプロセッサーをイールドすることを意味します。待機が短い場合はイールド mutex を、待機が長い場合はブロック mutex を使用します。
次は、mutex の動作の概要です。
spin_mutex は、非スケーラブル、アンフェア、非再帰で、ユーザー空間でスピンします。一見、不適切な機能のようですが、競合範囲がわずかな 場合は非常に高速 です。多くの spin_mutex オブジェクトに競合状態を分散できるようなプログラム設計が可能な場合は、他の mutex を使用してパフォーマンスを向上させることができます。mutex の競合状態が激しい場合は、いずれにしてもそのアルゴリズムではパフォーマンスは向上しません。効率的なロックを探すよりも先に、アルゴリズムの再設計を検討してください。
queuing_mutex は、スケーラブル、フェア、非再帰で、ユーザー空間でスピンします。スケーラビリティーと公平性が重要な場合に使用してください。
spin_rw_mutex と queuing_rw_mutex は、spin_mutex と queuing_mutex に似ていますが、リーダー ロックをサポートします。
speculative_spin_mutex と speculative_spin_rw_mutex は、spin_mutex と spin_rw_mutex に似ていますが、ハードウェア・トランザクショナル・メモリーをサポートするプロセッサーでスペキュレーティブ・ロック を提供します。スペキュレーティブ・ロックは、非スペキュレーティブ・ロックと異なる結果を生成する「競合」がない限り、複数のスレッドで同じロックを取得して動作します。競合レートが低い場合 (つまり、ほとんどスペキュレーティブ・ロック・モードの場合)、これらの mutex はスケーラブル です。
mutex と recursive_mutex は、「ネイティブ」環境の排他制御を行うラッパーです。Windows® システムでは、CRITICAL_SECTION の上に実装されます。Linux* および OS X* システムでは、pthread mutex の上に実装されます。このラッパーを使用する利点は、例外セーフなインターフェイスを追加し、インテル® TBB のほかの mutex と同一のインターフェイスを提供することです。後でパフォーマンス測定を行い、必要であれば、別の mutex に簡単に切り替えることができます。
null_mutex と null_rw_mutex は、何もしません。テンプレート引数として使用します。例えば、コンテナー・テンプレートを定義していて、一部のインスタンス化は複数のスレッドで共有され内部ロックが必要ですが、ほかのインスタンス化はスレッドに対してプライベートでロックする必要がないと仮定します。この場合、mutex 型の引数でテンプレートを定義できます。引数は、ロックが必要な場合は実際の mutex 型の 1 つで、ロックが必要ない場合は null_mutex になります。
mutex |
スケーラブル |
フェア |
再帰 |
長い待機 |
サイズ |
---|---|---|---|---|---|
mutex |
OS 依存 |
OS 依存 |
X |
ブロック |
3 ワード以上 |
recursive_mutex |
OS 依存 |
OS 依存 |
✓ |
ブロック |
3 ワード以上 |
spin_mutex |
X |
X |
X |
イールド |
1 バイト |
speculative_spin_mutex |
HW 依存 |
X |
X |
イールド |
2 キャッシュライン |
queuing_mutex |
✓ |
✓ |
X |
イールド |
1 ワード |
spin_rw_mutex |
X |
X |
X |
イールド |
1 ワード |
speculative_spin_rw_mutex |
HW 依存 |
X |
X |
イールド |
3 キャッシュライン |
queuing_rw_mutex |
✓ |
✓ |
X |
イールド |
1 ワード |
null_mutex[6] |
- |
✓ |
✓ |
- |
空 |
null_rw_mutex |
- |
✓ |
✓ |
- |
空 |
[5] イールドは、Microsoft® Windows® システムでは SwitchToThread()、別のシステムでは sched_yield() で実装されます。
[6] null mutex はスタベーションを起こさないため、インテル® TBB ではフェアと見なされています。非スタティックなデータメンバーはありません。