none
Moniter.Enter()の処理に時間がかかる RRS feed

  • 質問

  • いつもお世話になっております。

    Windowsアプリケーションで、クライアントからソケットで受信し、処理を行い、ソケットでクライアント
    応答するというものを作りました。
    このアプリケーションは、.NETFramework1.0(VisualStudio2002でビルド)で構築しました。

    このアプリケーションが稼働していたサーバが老朽のため、新しいサーバに移行しました。

    【問題】
    新しいサーバでこのアプリケーション(プログラムの修正なし)、実行したら、Moniter.Enter()の処理に時間がかかるという現象が発生しました。
    クライアントからのソケット受信後のMoniter.Enterを行ったときに100ms~7s、処理に時間がかかるようです。
    (Moniter.Enterがオブジェクトに対する相互排他を取得できない?)
    頻度は、非同期で、時間がかからない(16ms以内)場合もあります。
    20回に1回ぐらいの頻度で起きるようです。

    ※旧サーバは、現象が発生しません。(5年間)
    ※社内のサーバは、現象が発生しません。
    ※64bitのサーバでは、現象が発生しません。

    ※.NETFramework1.1、.NETFramework2.0でビルドしても現象は改善されませんでした。
    ※このプロセスのCPU数を限定してみましたが、この現象は改善されませんでした。
    (CPU4個を指定、CPU2個を指定、CPU1個を指定でテストしました。)

    ↓プログラムの概略を以下に記載します。

    /**********************************************************************/
        /// <summary>
        /// スレッド管理クラス
        /// </summary>
        public class ThreadManager
        {
            public const int MAX_CLIENT_THREAD = 50;   // 最大スレッド数
            public static int THREAD_SLEEP_TIME = 500;  // スレッドスリープ時間
            public ClientRecive[] ClientRecive;               // 受信処理クラス[n]
            public Client[] Client;                                // クライアント処理クラス[n]

            /// <summary>
            /// コンストラクタ
            /// </summary>
            public ThreadManager()
            {
                ClientRecive = new ClientRecive[MAX_CLIENT_THREAD];
                Client = new Client[MAX_CLIENT_THREAD];

                for (int i = 0; i < MAX_CLIENT_THREAD; i++)
                {
                    ClientRecive[i] = new ClientRecive(this, i);
                    Client[i] = new Client(this, i);
                }

            }

        }

        /// <summary>
        /// 受信処理クラス
        /// </summary>
        public class ClientRecive
        {
            public int Number;                       // クライアント番号
            public Thread MyThread;              // 自スレッド
            private ThreadManager TMangaer; // スレッド管理クラス

            /// <summary>
            /// コンストラクタ
            /// </summary>
            public ClientRecive(ThreadManager argTManager, int argNumber)
            {
                TMangaer = argTManager;
                Number = argNumber;
                MyThread = new Thread(new ThreadStart(Recive));
                MyThread.Start();
            }

            /// <summary>
            /// 受信
            /// </summary>
            public void Recive()
            {
                string str = "";
                int queueCount = 0;

                try
                {
                    while (MyThread.IsAlive)
                    {
                        // 【・・・ソケットの受信処理】
                       
                        // ↓の Monitor.Enterの処理で100ms~7s、処理に時間がかかるようです。
                        // 頻度は、非同期で、時間がかからない(16ms以内)場合もあります。20回に1回ぐらいの頻度で起きるようです。
                        Monitor.Enter(TMangaer.Client[Number].MyThread);

                        TMangaer.Client[Number].Que.Enqueue(str);

                        queueCount = Que.Count();

                        if (queueCount <= 1)
                        {
                            TMangaer.Client[Number].SyncWaitor.Set();
                        }

                        Monitor.Exit(TMangaer.Client[Number].MyThread);

                    }

                }
                catch (Exception exc)
                {
                    // 【・・・処理】
                }
                finally
                {
                    // 【・・・処理】
                }

            }

        }

        /// <summary>
        /// クライアント処理クラス
        /// </summary>
        public class Client
        {
            public int Number;                            // クライアント番号
            public Thread MyThread;                   // 自スレッド
            public ThreadManager TMangaer;       // スレッド管理クラス
            public AutoResetEvent SyncEvent;    // 同期イベント
            public Queue Que;                          // キュー
     
            /// <summary>
            /// コンストラクタ
            /// </summary>
            public Client(ThreadManager argTManager, int argNumber)
            {
                TMangaer = argTManager;
                Number = argNumber;
                SyncEvent = new AutoResetEvent(false);
                Que = new Queue();
                MyThread = new Thread(new ThreadStart(Execute));
                MyThread.Start();
            }

            /// <summary>
            /// 実行
            /// </summary>
            public void Execute()
            {
                bool end = false;
                string str = "";
                int queueCount = 0;

                try
                {
                    while (MyThread.IsAlive && end == false)
                    {
                        Monitor.Enter(ThreadManager.Client[Number].MyThread);
                        queueCount = Que.Count();
                        Monitor.Exit(ThreadManager.Client[Number].MyThread);

                        if (queueCount == 0)
                        {
                            if (SyncEvent.WaitOne(ThreadManager.THREAD_SLEEP_TIME, false))
                            {
                                continue;
                            }
                        }

                        while (end == false)
                        {
                            // キュー内のジョブがなくなるまで処理を繰り返す。

                            Monitor.Enter(ThreadManager.Client[Number].MyThread);
                            queueCount = Que.Count();
                            Monitor.Exit(ThreadManager.Client[Number].MyThread);
                                                  
                            if (queueCount == 0)
                            {
                                break;
                            }
                            else
                            {
                                str = Que.Peek();

                                // 【・・・処理】

                                Monitor.Enter(ThreadManager.Client[Number].MyThread);
                                Que.Dequeue();
                                Monitor.Exit(ThreadManager.Client[Number].MyThread);
                            }

                        }

                    }

                }
                catch
                {
                    // 【・・・処理】
                }
                finally
                {
                    // 【・・・処理】
                }

            }
           
        }

    /**********************************************************************/

    【旧サーバのスペック】
    CPU : インテル®E7501 インテル®Xeon®3.20GHzプロセッサ 1MB L3キャッシュ × 2 【2CPU】
    メモリー : 4GB(4×1GB)
    OS : Windows Server 2000 SP4

    【新サーバのスペック】(現象が起きるサーバ)
    CPU : クアッドコア インテル®Xeon®プロセッサーE5420(2×6MB L2キャッシュ 2.50GHz 1333MhzFSB)× 2 【8CPU】
    メモリー : 4GB(4×1GB 1R)667Mhz Fully Buffered DIMM ECC DRR2メモリ
    OS : Windows Server 2003 R2 Standard Edition with SP2 日本語版

    【社内サーバのスペック】
    CPU : クアッドコア インテル®Xeon®プロセッサーX5365 @3.00GHZ
    メモリー : 4GB
    OS : Windows Server 2003 R2 Standard Edition with SP2 日本語版

    【64bitのサーバのスペック】
    CPU : クアッドコア インテル®Xeon®プロセッサーE5420(2×6MB L2キャッシュ 2.50GHz 1333MhzFSB)× 2 【8CPU】
    メモリー : 4GB(4×1GB 1R)667Mhz Fully Buffered DIMM ECC DRR2メモリ
    OS : Windows Server 2003 R2 Standard Edition with SP2 日本語版(64bit)


    このような現象の理由と解決方法を、ご存知の方がいましたら教えて頂きたいです。
    宜しくお願いします。

     

    2009年4月16日 5:24

すべての返信

  • 環境依存で起きたり、起きなかったりする以上は、コードをぱっと見ても分からない問題なのでしょう。
    例えばロック状態のログを取るとか、どういった動きをしているか掴むための工夫をして、何が本当の問題なのか掴むように動いてみませんか?

    # マルチスレッドで動かしたときにヤバそうに見える箇所がちらほらあるような気がしますが…、5年も問題なく動くってことは杞憂なんかなぁ。


    解決した場合は、参考になった返信に「回答としてマーク」のボタンを利用して、回答に設定しましょう(複数に設定できます)。
    2009年4月16日 14:42
    モデレータ
  • 他の環境でも発生しているものの見逃しているだけの可能性もありますね。
    リファクタリングは目的ではないのでしょうが、こういう場合、Monitor.Pulseメソッド を使うようですね。ちょうどサンプルコードもQueueクラスを使っています。

    本題ですが、Threadクラスは内部でlock(this)を使用しています。つまりその処理の最中には「Monitor.Enter(ThreadManager.Client[Number].MyThread);」は待たされます。ただしそのThreadクラスの内部処理が7秒かかるとは思えないのでこれは直接の原因ではないと思います。
    2009年4月16日 14:59
  • 回答、ありがとうございました。

    >他の環境でも発生しているものの見逃しているだけの可能性もありますね。
    今のところ、他の環境でも、何年間も問題なく動いています。(24時間365日)

    >ただしそのThreadクラスの内部処理が7秒かかるとは思えないのでこれは直接の原因ではないと思います。

    時間は、「Monitor.Enter(ThreadManager.Client[Number].MyThread);」の前後にログを入れて、計った結果です。

    また、他のMonitorのメゾットを使用している箇所にも前後にログを入れてあります。


    2009年4月16日 23:59