1. ホーム
  2. c#

[解決済み】.NETにゾンビは存在するのか......?

2022-03-25 03:14:58

質問

チームメイトと.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は、このような影響を及ぼす可能性のあるシステム コンポーネントへの露出からスレッドを保護する何らかの処理を行うことが考えられるようですが、これは純粋に推測であり、おそらく私がこれまで「ゾンビ スレッド」という言葉を聞いたことがないという事実に基づいているだけでしょう。 ですから、この件に関するフィードバックをここで得られることを期待しています。

  1. ゾンビスレッドの明確な定義がありますか?
  2. .NETでゾンビスレッドは発生しますか? (なぜ/なぜそうしないのか?)
  3. 該当する場合、.NETでゾンビスレッドを強制的に作成するにはどうすればよいですか?
  4. 該当する場合、.NETでゾンビスレッドシナリオのリスクを負わずにロックを活用するにはどうすればよいですか?

更新情報

この質問をしたのは、2年ちょっと前です。 今日、こんなことがありました。

解決方法は?

  • ここで説明したものより、もっと明確なゾンビ・スレッドの定義がありますか?

終了した(したがって、もはやリソースを解放できない)スレッドですが、そのリソース(例えばハンドル)がまだ残っていて、(潜在的に)問題を引き起こしているのです。

  • .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 を使用していますが、これは SafeFileHandle を使用してファイルハンドルをラップしています)。

のロックの例は C.Evenhuisの回答 は、スレッドが変な方法で終了したときにリソース(この場合はロック)の解放に失敗する最も簡単な方法でしょうが、その場合は lock ステートメントで代用するか、リリースを finally ブロックを作成します。

以下もご参照ください。