私はチームメイトと .NET のロックについて議論していました。彼は本当に聡明で、低レベルと高レベルの両方のプログラミングの豊富な経験を持っていますが、低レベルプログラミングの経験は私よりはるかに上回っています。とにかく、彼は、高負荷が予想される重要なシステムでは、可能な限り .NET ロックは避けるべきであると主張しました。そうすることで、「ゾンビ スレッド」がシステムをクラッシュさせる可能性がわずかながらあることは認めますが、それを避けることができるからです。私はロックを日常的に使用していますが、「ゾンビ スレッド」が何なのかわからなかったので、尋ねてみました。彼の説明から私が受けた印象は、ゾンビ スレッドは終了したが、何らかの理由でまだリソースを保持しているスレッドであるというものでした。ゾンビ スレッドがシステムを破壊する例として彼が挙げたのが、スレッドが何らかのオブジェクトをロックした後に何らかの手順を開始し、その後ロックが解除される前にどこかの時点で終了するというものでした。この状況では、システムがクラッシュする可能性があります。なぜなら、最終的にそのメソッドを実行しようとすると、ロックされたオブジェクトを使用しているスレッドが終了しているため、すべてのスレッドが返されることのないオブジェクトへのアクセスを待機することになるからです。
この要点は理解できたと思いますが、もし的外れでしたらお知らせください。この概念は私には納得できました。これが .NET で実際に起こり得るシナリオであるとは、完全には納得できませんでした。これまで「ゾンビ」という言葉を聞いたことはありませんが、下位レベルで深く作業したプログラマーは、コンピューティングの基礎 (スレッドなど) を深く理解している傾向があることは認識しています。ただし、ロックの価値は確かに理解していますし、世界クラスのプログラマーの多くがロックを活用しているのを見てきました。また、このステートメントがlock(obj)
実際には次の構文の糖衣にすぎないことがわかっているため、自分でこれを評価する能力は限られています。
bool lockWasTaken = false;
var temp = obj;
try { Monitor.Enter(temp, ref lockWasTaken); { body } }
finally { if (lockWasTaken) Monitor.Exit(temp); }
および は とMonitor.Enter
と がMonitor.Exit
マークされているextern
ためです。.NET が、このような影響を与える可能性のあるシステム コンポーネントへのスレッドの露出を防ぐ何らかの処理を行っていると考えられますが、これは単なる推測であり、おそらく私が「ゾンビ スレッド」についてこれまで聞いたことがないという事実に基づいているだけでしょう。そこで、ここでこれについてフィードバックをいただければ幸いです。
- ここで説明したものよりも明確な「ゾンビスレッド」の定義はありますか?
- .NET でゾンビ スレッドが発生する可能性はありますか? (理由は何ですか?)
- 該当する場合、.NET でゾンビ スレッドを強制的に作成するにはどうすればよいですか?
- 該当する場合、.NET でゾンビ スレッド シナリオのリスクを冒さずにロックを活用するにはどうすればよいでしょうか。
アップデート
2年ちょっと前にこの質問をしました。今日、こんなことが起こりました。
ベストアンサー1
- ここで説明したものよりも明確な「ゾンビスレッド」の定義はありますか?
私にとっては、これはかなり良い説明のように思えます。スレッドは終了した(したがって、リソースを解放できなくなった)が、そのリソース(ハンドルなど)はまだ存在しており、(潜在的に)問題を引き起こしているのです。
- .NET でゾンビ スレッドが発生する可能性はありますか? (理由は何ですか?)
- 該当する場合、.NET でゾンビ スレッドを強制的に作成するにはどうすればよいですか?
確かにそうですよ、見てよ、私が作ったのよ!
[DllImport("kernel32.dll")]
private static extern void ExitThread(uint dwExitCode);
static void Main(string[] args)
{
new Thread(Target).Start();
Console.ReadLine();
}
private static void Target()
{
using (var file = File.Open("test.txt", FileMode.OpenOrCreate))
{
ExitThread(0);
}
}
Target
このプログラムは、ファイルを開いてすぐに自分自身を終了させるスレッドを開始します。ExitThread
.
結果として生じるゾンビ スレッドは "test.txt" ファイルへのハンドルを解放しないため、プログラムは終了するまでファイルは開いたままになります (プロセス エクスプローラーなどで確認できます)。
"test.txt" へのハンドルは が呼び出されるまで解放されませんGC.Collect
。ハンドルをリークするゾンビ スレッドを作成するのは、思ったよりも難しいことがわかりました)
- 該当する場合、.NET でゾンビ スレッド シナリオのリスクを冒さずにロックを活用するにはどうすればよいでしょうか。
私が今やったことと同じことをしないでください!
コードが適切にクリーンアップされていれば(安全なハンドルまたは、アンマネージ リソースで作業している場合は同等のクラス) を使用し、奇妙で素晴らしい方法でスレッドを強制終了しない限り (最も安全な方法は、スレッドを強制終了しないことです。スレッドが通常どおりに終了するか、必要に応じて例外を介して終了するようにします)、ゾンビ スレッドに似たものが発生する唯一の方法は、何かが非常にうまくいかない場合 (たとえば、CLR で何かがうまくいかない場合) です。
実際のところ、ゾンビ スレッドを作成するのは驚くほど困難です (ドキュメントで基本的に C の外部で呼び出さないように指示されている関数に P/Invoke する必要がありました)。たとえば、次の (ひどい) コードは実際にはゾンビ スレッドを作成しません。
static void Main(string[] args)
{
var thread = new Thread(Target);
thread.Start();
// Ugh, never call Abort...
thread.Abort();
Console.ReadLine();
}
private static void Target()
{
// Ouch, open file which isn't closed...
var file = File.Open("test.txt", FileMode.OpenOrCreate);
while (true)
{
Thread.Sleep(1);
}
GC.KeepAlive(file);
}
かなりひどい間違いを犯しているにもかかわらず、「test.txt」へのハンドルは呼び出されるとすぐに閉じられます(裏ではAbort
ファイナライザの一部としてfile
セーフファイルハンドルファイルハンドルをラップする)
ロックの例C.Evenhuisの回答これはおそらく、スレッドが異常でない方法で終了したときにリソース (この場合はロック) を解放できない最も簡単な方法ですが、lock
代わりにステートメントを使用するか、解放をfinally
ブロック内に配置すると簡単に修正できます。
参照
- C# IL コード生成の微妙な点キーワードを使用していても例外によってロックが解除されない可能性がある非常に微妙なケース
lock
(ただし、.Net 3.5 以前のみ) - ロックと例外は混在しない