スレッドのロック
スレッドのロックは、共有リソースへのアクセスを保護するために使用されているクリティカル セクション オブジェクトなどのスレッド プリミティブを待機している間にスレッドがストールすると発生します。 2 つのスレッドが同時に共有リソースにアクセスしようとした場合、どちらかのスレッドは一時停止状態に入ります。 (詳細については、「Understanding thread states (スレッドの状態の概要)」を参照してください。) 一時停止されたスレッドは、共有リソースが利用可能になるまでは実行を続けることができません。
PIX
PIX のタイムライン ウィンドウでは、スレッドがいつ実行されるか、どのコアで実行されるか、なぜ切り替えられたのかを監視することにより、どこでスレッドがロックされているのかを確認できます。
コード内でスレッドがロックしている場所を特定するには
タイミング キャプチャを生成する一般的な手順を実行します。
図 1. タイミング キャプチャが表示されている PIX のタイムライン ウィンドウ
図 1 に示されている赤い目盛り線は、コンテキストの切り替えを示しています。 赤い部分が太くなっている箇所では、複数のコンテキストの切り替えが近接しています。
複数のコンテキストの切り替えを含む関心のある領域を拡大します。
図 2: PIXの [タイムライン] ウィンドウの展開されたセクション
注意
各タイムライン ラベルの左にある歯車のアイコンを選択すると、コンテキストの切り替えを強調して見やすくするなどの表示オプションを表示することができます。
図 2 では、各スレッド タイムラインの上部のバーの端にある垂直方向の明るい赤い線に注目してください。 それぞれの線は、コンテキストの切り替えを表しています。
コンテキストの切り替えの上にカーソルを置くと、基本的な情報が表示されます。 コンテキストの切り替えから実行を開始したスレッドが別のスレッドによってレディ状態となった場合、図 3 に示されているように、2 つのスレッド タイムラインの間に矢印が表示されます。
図 3. コンテキストの切り替えの詳細を表示する PIX のタイムライン ウィンドウ
コンテキストの切り替えの線をクリックして選択します。 図 4 で示されているように、[タイムライン] タブの左下にある要素の詳細ウィンドウを見てみましょう。 ウィンドウには、切り替え元のスレッド (From)、切り替え先のスレッド (To)、利用可能な場合にはレディ状態への変更元スレッドが表示されます。 この例では、コンテキストの切り替えはアイドル状態からスレッド 792 となり、スレッド 756 によってレディ状態へと変更されました。
図 4. 呼び出し履歴を示すコンテキストの切り替え用の PIX の要素の詳細ウィンドウ
To 呼び出し履歴では、スレッド 792 が Windows イベント オブジェクトのラッパーである
ATG::EventLockable
オブジェクト上のlock
への呼び出しから再開されたことがわかります。 準備中のスレッド 756 が、同じオブジェクト上のunlock
を呼び出しました。 どちらのスレッドも同じAcquireLockAndSpin
関数を呼び出しました。 この例では、コンテキストの切り替えは、スレッド 792 が待機している Windows イベントを設定したスレッド 756 によってレディ状態へと変更されました。注意
すべてのコンテキスト スイッチに "他のスレッドによって準備完了になった" ことを示すデータがあるわけではありません。 たとえば、スリープ状態が解除されたスレッドには、コンテキストの切り替えに一覧表示されている "レディ状態への変更元" スレッドはありません。
WPA
また、Windows Performance Analyzer (WPA) を使用して、スレッドがロックしている場所を見つけることもできます。 ロックされているスレッドの検出に最も役に立つ 2 つのビューは、[CPU Usage (Precise) Timeline by Process, Thread] および [CPU Usage (Precise) Timeline by CPU] です。どちらも ThreadLocking.wpaProfile という名前の WPA プロファイルにあります。 これらのビューはコンテキスト スイッチに基づいており、スレッドが切り替えられたタイミングや、新しいスレッドのコール スタックを正確に表示します。
一般的な手順を実行して、イベント トレース ログ (ETL) ファイルを生成します。
ThreadLocking.wpaProfile WPA プロファイルを適用します。 [New analysis] (新しい分析) タブは、図 5 のようになります。
図 5. WPA のスレッドのロック プロファイルの既定のビュー
これら 2 つのビューは、同じデータを表示するための異なる方法にすぎません。 問題が特定のスレッドに関係しているのか、または CPU コアに関係しているのかに応じて、それぞれのビューにそれぞれの利点があります。 この例では、[Process, Thread] ビューのみを使用します。 ただし、問題がより一般的なものであったり、CPU コアにローカライズされているものであったりする場合には、[CPU] ビューで同じ手順を使用することができます。
関心のあるスレッドを選択します。 そのスレッドが実行されているすべての期間がグラフ上で強調表示されます。
タイムラインのセクションを拡大します。 これは、実行している可能性がある他のスレッドからのノイズを除去するのに役立ちます。 開始地点としては、単一のフレームが適しています。
長い時間にわたって切り替えられた後に実行を開始したばかりのスレッドの場所、またはかなりの回数にわたり開始や停止が行われたスレッドの場所を探してください。 図 6 に示されているように、その場所を拡大します。
図 6. スレッドが処理から切り替えられた期間の拡大。
図 7 に示されているように、表内で一致するセクションを強調表示するには、グラフ内の特定のスレッドまたは期間を選択します。 新しいスレッド スタック (フレーム タグ) 列には、以前にスレッドが切り替えられたコードの呼び出し履歴が表示されます。カウント列には、現在タイムライン ビューに表示されているスレッドの部分のすべてのコンテキストの切り替えに、その呼び出し履歴のフレームが存在していた回数が表示されます。
図 7. スレッド 1084 のスレッド タイムラインでは、EventLockable::lock が WaitForSingleObjectEx を呼び出したため、4 回切り替えられたことが示されています。
スレッドは様々な理由で切り替えられる可能性があります。 様々な場所やスレッドを拡大することにより、コードの中で何が起こっているのかをより明確に把握することができます。 たとえば、スレッド 1084 にある 4 つのコンテキストの切り替えのそれぞれについても、スタックの最後にあるデータ テーブルのさらに下の方に個別に記載されています。 列の幅を調整することで、レディ状態のスレッド スタック列 (図 8) にある特定のコンテキストの切り替えの準備中のスレッドを示す情報を確認することができます。
図 8. スレッド 1084 の 1 つのコンテキストの切り替えが ATG::EventLockable::unlock を介してスレッド 1076 によってレディ状態へと変更されたことを示す WPA のデータ テーブル
スレッドのロックの一般的な原因
スレッドは、さまざまな理由によりロックされることがあります。 ここでは、その最も一般的な理由と、その対処法をご紹介します。
- スピン ロックでの SwitchToThread を使用するタイトなループ
- スピン ロックで発生している高い競合度。
- スピン カウントの拡張をご検討ください。
-
EnterCriticalSection の呼び出し
- クリティカル セクションで発生している高い競合度。
- スピン カウントの拡張をご検討ください。
-
WaitForSingleObject
- スレッド通信が多すぎます。
- プリミティブを必要としない別のアルゴリズムをご検討ください。
- メッセージをバッチ処理し、通信を最小限に抑えます。
- 実行する十分な作業がありません。
- ジョブの複雑さを増やすことをご検討ください。
- 新しいジョブの準備ができる前に終了してしまうジョブ。
- ブロックする前の
WaitForSingleObject
のスピンをご検討ください。
- ブロックする前の
- スレッド通信が多すぎます。
-
優先順位の逆転
- 優先順位の低いスレッドが、優先順位の高いスレッドで必要なロックを保持しています。