インタビューは5分前にありました。3つの質問には答えませんでした。誰か助けてください。
質問:
マルチスレッドアプリケーション機能でデッドロックシナリオを探して防止する方法
私が与えた答え:
デッドロックとロック、ミューテックス、モニター、セマフォの定義をしました。彼は私に、これらはツールであると言いましたが、デッドロックシナリオを探す方法は、これらのツールを盲目的に使用するとパフォーマンスが低下するためです。
これを理解してください。
パフォーマンス分析ツールは、特にデッドロックの特定にも役立ちます。この質問は、このトピックにいくつかの洞察を提供します: 競合状態/デッドロックを見つけるためのC#/。NET分析ツール 。
コードの視覚的な分析とロックの適切な使用も役立ちます(コードの検査中にコードの潜在的な問題を検出できるはずです)が、複雑なアプリケーションでは非常に難しい場合があります。デッドロックは、単にコードを検査するだけではなく、コードを実行したときにのみ表示される場合があります。
面接官のことはあまり知りません。ロック標準/ガイドラインについてどれだけ知っているかを知りたい人もいれば、ツールの使い方を知っているかどうかを知りたい人もいれば、両方を求めている人もいます。たとえば、私が働いている会社では、ツール(特に私たちがすでに所有して使用しているツール)の使用が高く評価されています。しかし、それは、そもそもコーディングデッドロックを防ぐスキルを持っているべきではないという意味ではありません。
ロックのためだけに何かをロックすると、スレッドはお互いに待機するため、パフォーマンスに影響します。ワークフローを分析して、どのタイプのロック(単純なlock
または ReaderWriterLockSlim
)を使用して、本当に何をロックする必要があるかを判別する必要があります。デッドロックを防ぐには、多くの典型的な方法があります。
たとえば、 ReaderWriterLockSlim
を使用する場合、タイムアウトを使用してデッドロックを防ぐことができます(かなり待機すると、ロックの取得を中止します)
if (cacheLock.TryEnterWriteLock(timeout))
{
...
}
そして、そのようなタイムアウトを提案できるはずです。
そのような質問では、ネストされたロックの悪用など、古典的なデッドロックのケースについて少なくとも言及されることを期待します(それらを回避できるか、なぜ悪いのかなどを知ることができるはずです)。
主題は非常に大きいです...あなたはこれについて何度も続けることができます。しかし、定義なし。ロックとは何かを理解することと、大規模マルチスレッドアプリケーションでロック/セマフォ/ミューテックスを使用することを理解することは、2つの異なることです。
デッドロックがどのように発生し、どのように防止できるかを説明するのに問題があったようです。
デッドロックは、各スレッド(2つ以上)が、すでに別のリソースによってロックされているリソースのロックを取得しようとすると発生します。リソース1でロックされたスレッド1は、リソース2でロックを取得しようとします。同時に、スレッド2はリソース2でロックを取得し、リソース1でロックを取得しようとします。2つのスレッドがロックを放棄することはないため、デッドロックになります。発生します。
デッドロックを回避する最も簡単な方法は、タイムアウト値を使用することです。 Monitorクラス(system.Threading.Monitor)は、ロックの取得中にタイムアウトを設定できます。
例
try{
if(Monitor.TryEnter(this, 500))
{
// critical section
}
}
catch (Exception ex)
{
}
finally
{
Monitor.Exit();
}
インタビューはあなたにちょっとした質問をしていると思います。静的分析を使用してデッドロックを防ぐことができれば...誰もデッドロックを抱えることはありません!
個人的には、デッドロックを探すとき、重要なセクションが関数呼び出しよりも長い関数を見つけることから始めます。例えば
void func(){
lock(_lock){
func2();
}
}
何が本当にはっきりしていないfunc2
が実行しています。多分それは同じスレッドでイベントをディスパッチします、それはイベントがまだクリティカルセクションの一部であることを意味します。多分それは別のロックでロックします。多分それはスレッドプールにディスパッチし、今は別のスレッドにあるため、再入可能ではありません!これらの種類の場所は、デッドロックシナリオが発生しているのを見始めることができる場所です:複数の非再入可能ロックの場所がある場合。
また、デッドロックシナリオをトレースするときに、バックトラックして、作成されたすべてのスレッドの場所を見つけようとします。各機能と実際に実行できる場所について考えます。不明な場合は、ロギングを追加して、関数呼び出しがどこから来たかをログに記録することも役立ちます。
また、ロックのないデータ構造を使用することでデッドロックを回避することもできます(ただし、使用するのと同じくらい必要です)。アクセスするたびに変更される可能性があるため、ロックのない構造へのアクセスを最小限に抑える必要があります。
別の回答で述べたように、タイムアウト付きのミューテックスを使用できますが、常に機能するとは限りません(コードがタイムアウトよりも長く機能する必要がある場合はどうでしょうか)。これは、インタビュアーが求めていたものかもしれないと別のコメントで述べられました。本番環境では、これはあまり良い考えではありません。タイムアウトは常に変化します。何かが実行されてタイムアウトに達するのに予想よりも時間がかかった可能性があります。私はそれをデッドロックさせ、プロセスダンプを取り、ロックを保持していたものを正確に見つけて問題を修正する方が良いと思います。もちろん、ビジネス要件がそれを許容できない場合は、スマートロックの配置の選択とともに、これを防御コーディング戦略の一部として使用できます。
alwaysをロックするというインタビューに同意しません。大きなパフォーマンスの問題が発生します。競合しないロック/ミューテックスなどは、OSに引き渡す前にまずスピンロックとしてテストし、スピンロックは安価です。
一般に、デッドロックを回避する最良の方法は、プログラムの流れを理解することです。新しいロックオブジェクトを導入するたびに、それがどこで使用され、何がチェーンの下で何を使用するかを考えます。
この問題の最も簡単な解決策は、常に十分に大きなタイムアウトでスリープ/待機することです。そのタイムアウトが発生した場合、何かが本来よりも長くかかったこと、そしてデッドロックまたは別のバグの可能性が高いことがわかります。
Mutex m; // similar overloads exist for all locking primitives
if (!m.WaitOne(TimeSpan.FromSeconds(30)))
throw new Exception("Potential deadlock detected.");
WaitOne
がfalse
を返すとき、それは30秒待っていて、ロックはまだ解放されていませんでした。すべてのロックされた操作がミリ秒以内に完了する必要があることがわかっている場合(そうでない場合は、タイムアウトを増やしてください)、何かがうまくいかなかったことを示す非常に良い兆候です。