11

デバッグモード(F5)のVisual Studio2010IDEのWindowsServerGoDaddyVPSで実行されている.NET2.0コンソールアプリケーションがあります。

アプリケーションは定期的にフリーズします(ガベージコレクターが一時的に実行を一時停止したかのように)が、まれに実行を再開することはありません。

私はこれを何ヶ月も悩ませてきました、そしてアイデアが不足しています。

  • アプリケーションは可能な限り高速に実行されますが(100%のCPU使用率を使用します)、通常の優先順位で実行されます。また、マルチスレッドです。
  • アプリケーションがフリーズしたら、VS2010 IDEを使用して、プロセスを一時停止/一時停止解除することで、アプリケーションのフリーズを解除できます(デバッガーで実行されているため)。
  • 凍結されたプロセスを一時停止したときの最後の実行の場所は、無関係のようです。
  • 凍結している間、CPU使用率は100%のままです。
  • 解凍すると、次のフリーズまで完全に正常に動作します。
  • サーバーはフリーズの間に70日間稼働する場合もあれば、24時間しか稼働しない場合もあります。
  • メモリ使用量は比較的一定のままです。いかなる種類のメモリリークの証拠もありません。

正確に何が起こっているのかを診断するためのヒントはありますか?

4

3 に答える 3

16

また、マルチスレッドです

それが問題の重要な部分です。マルチスレッドプログラムが誤動作する可能性がある非常に典型的な方法を説明しています。スレッド化の典型的な問題の 1 つであるデッドロックが発生しています。

情報からさらに絞り込むことができます.100%のCPUを消費しているため、プロセスが完全に凍結されていないことは明らかです. おそらく、コードにホットな待機ループ、つまりイベントを通知する別のスレッドでスピンするループがあります。これは、特に厄介な種類のデッドロック、ライブロックを誘発する可能性があります。ライブロックはタイミングに非常に敏感です。コードの実行順序を少し変更すると、ライブロックが発生する可能性があります。そして、また戻ってください。

ライブロックは、デバッグしようとすると状態が消えるため、デバッグが非常に困難です。デバッガーを接続したり、コードを壊したりするのと同じように、スレッドのタイミングを変更して、条件から外すのに十分です。または、ログ ステートメントをコードに追加します。これは、スレッドの問題をデバッグするための一般的な戦略です。これにより、ログのオーバーヘッドが原因でタイミングが変更され、ライブロックが完全に消える可能性があります。

コードに大きく依存しているため、SO のようなサイトからこのような問題を解決することは困難であり、不可能です。理由を見つけるには、多くの場合、コードを徹底的に確認する必要があります。そして、抜本的な書き直しも珍しくありません。頑張ってください。

于 2013-02-01T13:47:03.940 に答える
2

アプリケーションには「デッドロックの回復/防止」コードがありますか? つまり、タイムアウトでロックしてから、おそらくスリープ後に再試行しますか?

アプリケーションはエラー コード (戻り値または例外) をチェックし、どこかでエラーが発生した場合に繰り返し再試行しますか?

このようなループは、コードが一部のイベント ハンドラーのみにあるイベント ループでも発生する可能性があることに注意してください。独自のコードで実際のループである必要はありません。これはおそらく当てはまりませんが、アプリケーションがフリーズしている場合は、イベント ループがブロックされていることを示しています。

上記のような問題がある場合は、タイムアウトとスリープをランダムな間隔にすることで問題を軽減し、エラーがデッド/ライブロックを生成する可能性がある場合に短いランダム期間のスリープを追加することができます。そのようなループがパフォーマンスに敏感な場合は、カウンターを追加し、ランダムにのみスリープを開始します。おそらく、再試行が何度か失敗した後、間隔を増やします。また、何かがロックされている間は、追加するスリープがスリープしないことを確認してください。

状況がより頻繁に発生する場合は、これを使用してコードを分割し、どのループが原因であるかを特定することもできます (100% の CPU 使用率は、いくつかの非常にビジーなループがスピンしていることを意味するため)。しかし、問題の希少性から、問題が実際に解消されれば、あなたは幸せになると思います ;)

于 2013-02-07T13:45:17.617 に答える
0

さて、ここで3つのこと...

まず、.NET のサーバー GC の使用を開始します: http://msdn.microsoft.com/en-us/library/ms229357.aspx。これにより、おそらくアプリケーションがブロックされないようになります。

次に、VM でそれを実行できる場合: 更新プログラムを確認します。これは常に明らかなように思えますが、単純な Windows の更新で奇妙な問題が修正されるケースを数多く見てきました。

3 番目に、ここで問題の 1 つである可能性があるオブジェクトの有効期間について指摘したいと思います。これは何が起こるかというかなり長い話なので、我慢してください。

オブジェクトの寿命は、基本的に構築 - ガベージ コレクション - ファイナライズです。3 つのプロセスはすべて別のスレッドで実行されます。GC は、「デストラクタ」を呼び出すキューを持つファイナライズ スレッドにデータを渡します。

奇妙なことをするファイナライザーがある場合は、次のように言います。

public class FinalizerObject
{
    public FinalizerObject(int n)
    {
        Console.WriteLine("Constructed {0}", n);
        this.n = n;
    }

    private int n;

    ~FinalizerObject()
    {
        while (true) { Console.WriteLine("Finalizing {0}...", n); System.Threading.Thread.Sleep(1000); }
    }
}

ファイナライザーはキューを処理する別のスレッドで実行されるため、1 つのファイナライザーで愚かなことを行うと、アプリケーションにとって深刻な問題になります。上記のクラスを 2 回使用すると、これを確認できます。

    static void Main(string[] args)
    {
        SomeMethod();
        GC.Collect(GC.MaxGeneration);
        GC.WaitForFullGCComplete();
        Console.WriteLine("All done.");
        Console.ReadLine();
    }

    static void SomeMethod()
    {
        var obj2 = new FinalizerObject(1);
        var obj3 = new FinalizerObject(2);
    }

メイン スレッドがまだ応答しているにもかかわらず、小さなメモリ リークが発生し、CPU プロセスが 100% の状態で Thread.Sleep を削除した場合に注意してください。これらは異なるスレッドであるため、ここからはプロセス全体を非常に簡単にブロックできます。たとえば、ロックを使用します。

    static void Main(string[] args)
    {
        SomeMethod();
        GC.Collect(GC.MaxGeneration);
        GC.WaitForFullGCComplete();
        Thread.Sleep(1000);
        lock (lockObject)
        {
            Console.WriteLine("All done.");
        }
        Console.ReadLine();
    }

    static object lockObject = new Program();

    static void SomeMethod()
    {
        var obj2 = new FinalizerObject(1, lockObject);
        var obj3 = new FinalizerObject(2, lockObject);
    }

    [...]

    ~FinalizerObject()
    {
        lock (lockObject) { while (true) { Console.WriteLine("Finalizing {0}...", n); System.Threading.Thread.Sleep(1000); } }
    }

だから、「本気ですか?」と考えているのがわかります。実際には、これに気付かずにこのようなことをしている可能性があります。これが「収量」の出番です。

「yield」の IEnumerable は実際には IDisposable であるため、IDisposable パターンを実装します。「yield」実装をロックと組み合わせ、「MoveNext」などで列挙して IDisposable を呼び出すのを忘れると、上記を反映したかなり厄介な動作が発生します。特に、ファイナライザーは別のスレッド (!) によってファイナライズ キューから呼び出されるためです。それを無限ループまたはスレッドの安全でないコードと組み合わせると、かなり厄介な予期しない動作が発生し、例外的な場合 (メモリが不足したとき、または GC が何かを行う必要があるとき) にトリガーされます。

言い換えれば、私はあなたのディスポーザブルとファイナライザーをチェックし、それらについて非常に批判的です. 「yield」に暗黙的なファイナライザーがあるかどうかを確認し、同じスレッドから IDisposable を呼び出していることを確認してください。あなたが警戒しなければならないことのいくつかの例:

    try
    {
        for (int i = 0; i < 10; ++i)
        {
            yield return "foo";
        }
    }
    finally
    {
        // Called by IDisposable
    }

    lock (myLock) // 'lock' and 'using' also trigger IDisposable
    {
        yield return "foo";
    }
于 2013-02-08T09:12:21.690 に答える