web-dev-qa-db-ja.com

.NETにはゾンビが存在しますか?

.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.EnterMonitor.Exitexternとマークされているためです。 .NETは、この種の影響を与える可能性のあるシステムコンポーネントへの露出からスレッドを保護する何らかの処理を行うと考えられますが、それは単なる推測であり、おそらく「ゾンビスレッド」を聞いたことがないという事実に基づいています。前。だから、私はこれについてのフィードバックをここで得られることを望んでいます:

  1. ここで説明したものよりも「ゾンビスレッド」の明確な定義はありますか?
  2. ゾンビスレッドは.NETで発生しますか? (なぜ/そうでないのか?)
  3. 該当する場合、どうすれば.NETでゾンビスレッドを強制的に作成できますか?
  4. 該当する場合、.NETのゾンビスレッドシナリオを危険にさらすことなくロックを活用するにはどうすればよいですか?

更新

私はこの質問を2年ちょっと前に尋ねました。今日これが起こった:

Object is in a zombie state.

368
smartcaveman
  • ここで説明したよりも「ゾンビスレッド」の明確な定義はありますか?

私にとってはかなり良い説明のようです-スレッドは終了しました(したがって、リソースを解放できなくなりました)が、そのリソース(ハンドルなど)はまだ存在し、(潜在的に)問題を引き起こしています。

  • ゾンビスレッドは.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のゾンビスレッドシナリオを危険にさらすことなくロックを活用するにはどうすればよいですか?

私がやったことをしないでください!

コードがそれ自体を正しくクリーンアップする限り( Safe Handles またはアンマネージリソースを使用する場合は同等のクラスを使用します)、そして奇妙でスレッドを殺すためにあなたの邪魔をしない限り素晴らしい方法(最も安全な方法は、スレッドを殺さないことです-必要に応じて、通常どおり、または必要に応じて例外を介して自分自身を終了させます)、ゾンビスレッドに似たものを持つ唯一の方法は、何かがなくなった場合ですveryが間違っています(たとえば、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);
}

かなりひどいミスを犯しましたが、Abortが呼び出されるとすぐに "test.txt"のハンドルが閉じられます(fileのファイナライザーの一部として、内部で SafeFileHandle を使用してファイルハンドルをラップします) )

C.Evenhuis answer のロックの例は、スレッドが異常な方法で終了したときにリソース(この場合はロック)の解放に失敗する最も簡単な方法ですが、簡単に修正できます代わりにlockステートメントを使用するか、finallyブロックにリリースを配置します。

こちらもご覧ください

232
Justin

回答を少し整理しましたが、参照用に元の回答を残しました

ゾンビという言葉を聞いたのは初めてなので、その定義は次のようになります。

すべてのリソースを解放せずに終了したスレッド

したがって、その定義があれば、他の言語(C/C++、Java)と同様に、.NETでそれを行うことができます。

ただし、これは.NETでスレッド化されたミッションクリティカルなコードを記述しない正当な理由とは思いません。 .NETに対して決定する他の理由があるかもしれませんが、ゾンビスレッドを持つことができるという理由だけで。ゾンビスレッドはC/C++で可能です(Cで台無しにする方がはるかに簡単だと主張します)。また、C/C++には多くの重要なスレッドアプリがあります(大量取引、データベースなど)。

結論使用する言語を決定している場合は、パフォーマンス、チームスキル、スケジュール、既存のアプリとの統合など、全体像を考慮することをお勧めします。もちろん、ゾンビスレッドCのような他の言語と比較して、.NETで実際にこの間違いを犯すのは非常に難しいので、この懸念は上記のような他のものによって隠されると思います。幸運を!

元の回答ゾンビ 適切なスレッド化コードを書かないと存在できます。 C/C++やJavaのような他の言語でも同じことが言えます。ただし、これは.NETでスレッドコードを記述しない理由ではありません。

他の言語と同様に、何かを使用する前に価格を知っておいてください。また、潜在的な問題を予測できるように、ボンネットの下で何が起こっているかを知ることも役立ちます。

ミッションクリティカルなシステムの信頼性の高いコードは、どの言語を使用していても簡単に作成できません。しかし、.NETで正しく実行することは不可能ではないことを確信しています。また、.NETのスレッド化はC/C++のスレッド化とそれほど変わらず、いくつかの.net固有の構造(RWLやイベントクラスの軽量バージョンなど)を除き、同じシステムコールを使用(またはビルド)します。

初めてゾンビという言葉を聞いたことがありますが、あなたの説明に基づいて、あなたの同僚はおそらくすべてのリソースを解放せずに終了したスレッドを意味しました。これにより、デッドロック、メモリリーク、またはその他の悪影響が発生する可能性があります。これは明らかに望ましくありませんが、。可能性のために.NETを単独で使用することは、他の言語でも可能であるため、おそらく良い考えではありません。 C/C++の方が.NETよりも混乱しやすい(特にRAIIがないCの場合)のは簡単ですが、重要なアプリの多くはC/C++で書かれていると私は主張しますか?だからそれは本当にあなたの個々の状況に依存します。アプリケーションからすべてのオンスの速度を抽出し、できるだけベアメタルに近づけたい場合、.NETmightは最適ではありません解決。予算が限られていて、Webサービスや既存の.netライブラリなどと多くのインターフェイスを使用している場合、.NETが適切な選択になる可能性があります

44
Jerahmeel

現在、私の答えのほとんどは、以下のコメントによって修正されています。答えを削除しません 評判ポイントが必要だから コメントの情報は読者にとって価値があるためです。

Immortal Blueは、.NET 2.0以降のfinallyブロックはスレッドの中断の影響を受けないことを指摘しました。そして、Andreas Niedermairがコメントしたように、これは実際のゾンビスレッドではないかもしれませんが、次の例はスレッドの中止が問題を引き起こす可能性があることを示しています。

class Program
{
    static readonly object _lock = new object();

    static void Main(string[] args)
    {
        Thread thread = new Thread(new ThreadStart(Zombie));
        thread.Start();
        Thread.Sleep(500);
        thread.Abort();

        Monitor.Enter(_lock);
        Console.WriteLine("Main entered");
        Console.ReadKey();
    }

    static void Zombie()
    {
        Monitor.Enter(_lock);
        Console.WriteLine("Zombie entered");
        Thread.Sleep(1000);
        Monitor.Exit(_lock);
        Console.WriteLine("Zombie exited");
    }
}

ただし、lock() { }ブロックを使用する場合、finallyがそのように起動されると、ThreadAbortExceptionは引き続き実行されます。

結局のところ、次の情報は.NET 1および.NET 1.1でのみ有効です。

lock() { }ブロック内で他の例外が発生し、ThreadAbortExceptionブロックが実行される直前にfinallyが到着した場合、ロックは解除されません。既に述べたように、lock() { }ブロックは次のようにコンパイルされます:

finally 
{
    if (lockWasTaken) 
        Monitor.Exit(temp); 
}

生成されたfinallyブロック内で別のスレッドがThread.Abort()を呼び出すと、ロックが解除されない場合があります。

25
C.Evenhuis

これはゾンビスレッドに関するものではありませんが、Effective C#にはIDisposableの実装に関するセクション(項目17)があり、興味深いと思われるゾンビオブジェクトについて説明しています。

本自体を読むことをお勧めしますが、その要点は、IDisposableを実装するクラス、またはDesctructorを含むクラスがある場合、リソースの解放だけです。ここで他のことを行うと、オブジェクトがガベージコレクションされない可能性がありますが、どのような方法でもアクセスできなくなります。

以下のような例を示します。

internal class Zombie
{
    private static readonly List<Zombie> _undead = new List<Zombie>();

    ~Zombie()
    {
        _undead.Add(this);
    }
}

このオブジェクトのデストラクタが呼び出されると、それ自体への参照がグローバルリストに配置されます。つまり、プログラムの存続期間中、メモリ内に存続しますが、アクセスできません。これは、リソース(特に管理されていないリソース)が完全に解放されない可能性があり、あらゆる種類の潜在的な問題を引き起こす可能性があることを意味します。

より完全な例を以下に示します。 foreachループに達するまでに、Undeadリストにはそれぞれ画像を含む150個のオブジェクトがありますが、画像はGCされており、使用しようとすると例外が発生します。この例では、画像を保存しようとしても、高さや幅などの寸法を表示しようとしても、画像を操作しようとするとArgumentException(パラメータは無効です)が表示されます。

class Program
{
    static void Main(string[] args)
    {
        for (var i = 0; i < 150; i++)
        {
            CreateImage();
        }

        GC.Collect();

        //Something to do while the GC runs
        FindPrimeNumber(1000000);

        foreach (var zombie in Zombie.Undead)
        {
            //object is still accessable, image isn't
            zombie.Image.Save(@"C:\temp\x.png");
        }

        Console.ReadLine();
    }

    //Borrowed from here
    //http://stackoverflow.com/a/13001749/969613
    public static long FindPrimeNumber(int n)
    {
        int count = 0;
        long a = 2;
        while (count < n)
        {
            long b = 2;
            int prime = 1;// to check if found a prime
            while (b * b <= a)
            {
                if (a % b == 0)
                {
                    prime = 0;
                    break;
                }
                b++;
            }
            if (prime > 0)
                count++;
            a++;
        }
        return (--a);
    }

    private static void CreateImage()
    {
        var zombie = new Zombie(new Bitmap(@"C:\temp\a.png"));
        zombie.Image.Save(@"C:\temp\b.png");
    }
}

internal class Zombie
{
    public static readonly List<Zombie> Undead = new List<Zombie>();

    public Zombie(Image image)
    {
        Image = image;
    }

    public Image Image { get; private set; }

    ~Zombie()
    {
        Undead.Add(this);
    }
}

繰り返しになりますが、特にゾンビスレッドについて質問していることは承知していますが、質問のタイトルは.netのゾンビに関するものです。

22
JMK

負荷が高い重要なシステムでは、主にパフォーマンスが向上するため、ロックフリーコードを記述する方が適切です。 LMAX のようなものと、これについての優れた議論のために「機械的な同情」を活用する方法を見てください。ゾンビスレッドが心配ですか?これはエッジケースであり、それはただのバグであり、lockを使用しない十分な理由ではないと思います。

あなたの友人はただ空想にふさわしく、あいまいなエキゾチックな用語の知識を誇示しているように聞こえます! Microsoft UKでパフォーマンスラボを運営している間、.NETでこの問題のインスタンスに出くわすことはありませんでした。

20
James World

1.ここで説明したものよりも「ゾンビスレッド」の明確な定義はありますか?

私は「ゾンビスレッド」が存在することに同意します。それは、手放せず、まだ完全に死なないリソースが残っているスレッドで起こることを指す用語であるため、「ゾンビ」という名前です。この紹介の説明は、お金に関してはかなり正しいです!

2. .NETでゾンビスレッドを発生させることはできますか? (なぜ/そうでないのか?)

はい、発生する可能性があります。これは参照であり、実際にはWindowsでは「ゾンビ」と呼ばれています。 MSDNはデッドプロセス/スレッドに「ゾンビ」という単語を使用します

頻繁に起こることは別の話であり、あなたのコーディング技術と実践に依存します。スレッドロックが好きで、しばらくそれをやったあなたは、そのシナリオがあなたに起こることさえ心配しません。

はい、コメントで@KevinPankoが正しく言及しているように、「ゾンビスレッド」はUnixから来ているため、XCode-ObjectiveCで使用され、「NSZombie」と呼ばれ、デバッグに使用されます。ほぼ同じように動作します...唯一の違いは、コードで潜在的な問題になる可能性のある「Zombie Thread」の代わりに、死亡すべきオブジェクトがデバッグ用の「ZombieObject」になることです。

3
S.H.

ゾンビスレッドを簡単に作成できます。

var zombies = new List<Thread>();
while(true)
{
    var th = new Thread(()=>{});
    th.Start();
    zombies.Add(th);
}

これにより、スレッドハンドルがリークします(Join()の場合)。マネージドワールドに関する限り、これは単なるメモリリークです。

次に、実際にロックを保持する方法でスレッドを強制終了することは、後部の苦痛ですが可能です。相手のExitThread()が仕事をします。彼が見つけたように、ファイルハンドルはgcによってクリーンアップされましたが、オブジェクトの周りのlockはそうではありませんでした。しかし、なぜそうするのでしょうか?

0
Joshua