デッドロックをデバッグするときに何を探しますか?


25

最近、スレッドを多用するプロジェクトに取り組んでいます。私はそれらを設計してもいいと思います。ステートレス設計を可能な限り使用する、複数のスレッドが必要とするすべてのリソースへのアクセスをロックする、など。関数型プログラミングの私の経験は非常に役立ちました。

しかし、他の人のスレッドコードを読むと、混乱します。私は今、デッドロックをデバッグしています。コーディングスタイルとデザインは私の個人的なスタイルとは異なるため、潜在的なデッドロック状態を見つけるのは困難です。

デッドロックをデバッグするときに何を探しますか?


私は問題の具体的な答えではなく、デッドロックのデバッグに関するより一般的なポインタが欲しいので、ここではSOの代わりにこれを求めています。
マイケルK

私が考えることができる戦略は、(他のいくつかが指摘しているように)ロギングであり、実際に誰がロックを保持しているかのデッドロックグラフを調べています(一部についてはstackoverflow.com/questions/3483094/を参照してください)ポインター)および注釈のロック(clang.llvm.org/docs/ThreadSafetyAnalysis.htmlを参照)。それがあなたのコードでなくても、作者に注釈を追加するように説得しようとするかもしれません。彼らはおそらくバグを見つけて、その過程で(おそらくあなたのものも含めて)修正します。
ドンハッチ

回答:


23

状況が実際のデッドロックである場合(つまり、2つのスレッドが2つの異なるロックを保持しているが、少なくとも1つのスレッドが他のスレッドが保持するロックを望んでいる場合)、まずスレッドがロックを順序付ける方法に関するすべての先入観を放棄する必要があります。何も想定しない。あなたが見ているコードからすべてのコメントを削除したいと思うかもしれません。それらのコメントはあなたに当てはまらない何かを信じさせるかもしれないからです。これを十分に強調することは困難です。何も仮定しないでください。

その後、スレッドが他の何かをロックしようとする間に保持されるロックを決定します。可能であれば、スレッドがロックとは逆の順序でロック解除されるようにします。さらに良いことに、スレッドが一度に1つのロックのみを保持するようにしてください。

スレッドの実行を綿密に処理し、すべてのロックイベントを調べます。各ロックで、スレッドが他のロックを保持しているかどうかを確認し、保持している場合、どのような状況で、同様の実行パスを実行する別のスレッドが考慮中のロックイベントに到達できるかを決定します。

時間やお金がなくなる前に問題を見つけられない可能性は確かにあります。


4
+1うわー、それは悲観的です...しかし、それは真実ではありません。すべてのバグを見つけることができないのは当然です。提案をありがとう!
マイケルK

ブルース、あなたの「本当の行き詰まり」の特徴は私にとって驚くべきことです。2つのスレッド間のデッドロックは、それぞれが他のスレッドが保持するロックを待機しているときに発生すると考えました。また、1つのロックを保持しているスレッドが、現在別のスレッドによって保持されている2番目のロックを取得するために待機する場合も定義に含まれているようです。それは私にとって行き詰まりのように聞こえません。それは...ですか??
ドンハッチ

@DonHatch-言い回しが悪い。あなたが説明する状況はデッドロックではありません。私は、ロックAを保持しているスレッドがロックBを取得しようとしている間に、ロックBを保持しているスレッドがロックAを取得しようとしている状況をデバッグする厄介さを伝えたいと思っていました。または、状況はもっと複雑です。ロック取得の順序について非常にオープンな心を保つ必要があります。すべての仮定を調べます。何も信頼しません。
ブルースエディガー

コードを注意深く読み、すべてのロック操作を単独で調べることを提案する+1。単一のノードを注意深く調べることで複雑なグラフを見るのは、一度にすべてを見るよりもはるかに簡単です。コードをじっと見つめ、さまざまなシナリオを頭の中で実行するだけで、問題を何回見つけましたか。
ニュートピア

11
  1. 他の人が言ったように...あなたがロギングのための有用な情報を得ることができるなら、それが最も簡単なことなので最初にそれを試してください。

  2. 関係するロックを特定します。永久に待機するすべてのミューテックス/セマフォを、5分など途方もなく長い待機時間に変更します。タイムアウトしたらエラーをログに記録します。これにより、少なくとも、問題に関係するロックの1つの方向が示されます。タイミングのばらつきに応じて、運が良ければ数回実行すると両方のロックが見つかる場合があります。タイムアウト待機が最初の段階でどのように到達したかを特定できなかった後、関数失敗コード/条件を使用して擬似スタックトレースを記録します。これは、問題に関係するスレッドを識別するのに役立ちます。

  3. もう1つ試すことができるのは、mutex /セマフォサービスのラッパーライブラリを構築することです。どのスレッドが各ミューテックスを持ち、どのスレッドがミューテックスを待機しているかを追跡します。スレッドがブロックされている時間をチェックするモニタースレッドを構築します。妥当な期間でトリガーし、追跡している状態情報をダンプします。

ある時点で、単純な古いコード検査が必要になるでしょう。


6

最初のステップ(ペーターが言うように)はロギングです。私の経験では、これにはしばしば問題があります。大量の並列処理では、これはしばしば不可能です。毎秒10万ノードのノードを処理するニューラルネットワークで、似たようなものを一度デバッグする必要がありました。エラーは数時間後にのみ発生し、1行の出力でさえ物事が非常に遅くなり、数日かかっていました。ロギングが可能な場合は、データに集中するのではなく、プログラムのフローに集中し、どの部分で発生するかを確認します。各関数の先頭にある簡単な行で、適切な関数が見つかったら、それを小さなチャンクに分割します。

別のオプションは、バグをローカライズするためにコードとデータの一部を削除することです。クラスの一部のみを取得し、最も基本的なテストのみを実行する小さなプログラムを作成することもできます(もちろん、いくつかのスレッドで実行します)。実際の処理状態に関する出力など、guiに関連するすべてを削除します。(ユーザーインターフェースがバグの原因であることがよくありました)

コードでは、ロックの初期化と解放の間の制御の完全な論理フローに従うようにしてください。一般的なエラーは、関数の開始時にロックし、終了時にロックを解除するが、その間に条件付きのreturnステートメントがあることです。例外もリリースを妨げる可能性があります。


「例外がリリースを妨げる可能性があります」->スコープ変数を持たない言語は残念です:/
Matthieu M.

1
@Matthieu:変数のスコープを設定して実際に適切に使用することは、2つの異なることです。そして彼は、特定の言語に言及せずに、一般的に起こりうる問題を求めました。そのため、これは制御の流れに影響を与える可能性がある1つのことです。
トールステンミュラー

3

私の親友は、コード内の興味深い場所での印刷/ログステートメントです。これらは通常、異なるスレッド間のタイミングを乱すことなく、アプリ内で実際に何が起こっているかをよりよく理解するのに役立ち、バグの再現を妨げる可能性があります。

それが失敗した場合、私の唯一の残りの方法はコードを見つめて、さまざまなスレッドと相互作用のメンタルモデルを構築しようとし、明らかに起こったことを達成するための可能なクレイジーな方法を考えようとしています:-)しかし、私はしません自分を非常に経験のあるデッドロックスレイヤーと考えてください。うまくいけば、他の人がより良いアイデアを提供できるようになり、そこから私も学ぶことができます:-)


1
今日、このようなデッドロックをいくつかデバッグしました。トリックは、ロックを取得する前後に、関数、行番号、ファイル名、ミューテックス変数の名前を(トークン化することによって)出力するマクロでpthread_mutex_lock()をラップすることでした。pthread_mutex_unlock()についても同じことを行います。スレッドがフリーズしたことを確認したとき、最後の2つのメッセージを見るだけでした。ロックしようとしているが終了しない2つのスレッドがありました。あとは、実行時にこれを切り替えるメカニズムを追加するだけです。:
プルメネーター

3

まず、そのコードの作者を取得してみてください。彼はおそらく自分が書いたものを知っているでしょう。話しているだけでは問題を特定できない場合でも、少なくとも彼と一緒に座ってデッドロック部分を特定できます。これは、助けなしでコードを理解するよりもはるかに速くなります。

それに失敗すると、PéterTörökが言ったように、おそらくロギングが道です。私の知る限り、デバッガーはマルチスレッド環境で悪い仕事をしました。ロックがどこにあるのかを特定し、どのリソースが待機しているか、そしてどのような状態で競合状態が発生するかを把握してください。


いいえ、ここでのロギングは敵です-遅いログインを設定すると、プログラムの動作を、ロギングを有効にすると完全に正常に動作するプログラムを取得しやすくなりますが、ロギングをオフにするとデッドロックになります。マルチコアCPUではなく、シングルコアでプログラムを実行する場合に発生するのと同じ種類の問題。
gbjbaanb

@gbjbaanb、それはあなたの敵があまりにも過酷だと言っています。たぶんそれをあなたの親友だと言うのは正しいでしょう。このページで、コードの検査が失敗した後、ログを取ることが良い最初のステップであると言う他の人々に同意します。問題を簡単に解決できました。それ以外の場合は必ず他の方法に頼りますが、常に役立つとは限らないという理由だけで、仕事に最適なツールを試すことを避けるのは良いアドバイスではないと思います。
ドンハッチ

0

この質問は私を魅了します;)まず第一に、あなたはすべての実行で一貫して問題を再現することができたので、自分自身を幸運だと考えてください。毎回同じスタックトレースで同じ例外を受け取った場合、それはかなり簡単です。そうでない場合、スタックトレースをそれほど信頼せず、代わりにグローバルオブジェクトへのアクセスと実行中の状態変化を監視するだけです。


0

デッドロックをデバッグする必要がある場合は、すでに問題が発生しています。原則として、可能な限り短い時間ロックを使用するか、可能であればまったく使用しないでください。ロックを取得した後、重要なコードに移行するような状況は避けてください。

もちろんプログラミング環境に依存しますが、単一のスレッドからのみリソースにアクセスできるシーケンシャルキューのようなものを調べる必要があります。

そして、古いが間違いのない戦略があります。レベル0から始まる各ロックに「レベル」を割り当てます。レベル0のロックを取得する場合、他のロックは許可されません。レベル1のロックを取得した後、レベル0のロックを取得できます。レベル10のロックを取得した後、レベル9以下でロックを取得できます。

これを実行できない場合、デッドロックに陥るのでコードを修正する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.