6

奇妙な「問題」に遭遇しました。ネットワーク全体をpingスイープするアプリケーションを用意してください。ネットマスクが 255.255.0.0 (65k + アドレス) のネットワークに到達するまではうまく機能します。

次のようにpingを送信します。

    foreach (string str in ListContainingAddresses)
        {
            using (Ping ping = new Ping())
            {
                if (pingCounter == 10000) { Thread.Sleep(10000); pingCounter = 0; }
                //Make an eventhandler
                ping.PingCompleted += new PingCompletedEventHandler(pingCompleted);
                //Send the pings asynchronously
                ping.SendAsync(IPAddress.Parse(str), 1000);
                sentPings++;

                //This counts pings being sent out
                pingCounter++;
            }
        }

そして、次のように受け取ります。

    public void pingCompleted(object sender, PingCompletedEventArgs e)
    {
        //This counts recieved addresses 
        recievedIpAddresses++;

        if (e.Reply.Status == IPStatus.Success)
        {
            //Do something
        }
        else
        {
            /*Computer is down*/
        }
        //This checks if sent equals recieved
        if (recievedIpAddresses == sentPings )
        {
            //All returned
        }
    }

問題は、a)時々(非常にまれに)完了しない(条件が満たされていない)ことです。b) 数字が一致しない場合は完了しますか? 今送信および受信を印刷すると、それらは

    Sent: 65025 Recieved: 64990

それにもかかわらず、条件が満たされ、アプリケーションが続行されますか? なぜ、どのようにこれが起こっているのかわかりません。アプリケーションが 2 つの int を更新するためにコードが高速に実行されていますか? 途中でいくつかの ping が失われますか? 255 のアドレスを持つサブネットワークで試してみると、この問題は発生しません。.NET 3.5以降、変数の代わりにCountDownEventを使用できません

4

1 に答える 1

7

ロックはどこにでもありますか?それは私にはあなたの問題のように見えます。コードにあらゆる種類の競合状態とメモリ プロセッサ キャッシュの問題が見られる可能性があります。

lockを保護するために使用してみてくださいrecievedIpAddresses == sentPings

sentPings++;
//This counts pings being sent out
pingCounter++;

使用するlock

例えば:

private readonly object SyncRoot = new object();

public void MainMethod()
{
    foreach (string str in ListContainingAddresses)
    { ... }
    lock (SyncRoot) { sentPings++; }
    ....
}

public void pingCompleted(object sender, PingCompletedEventArgs e)
{
    //This counts recieved addresses 
    lock (SyncRoot) { recievedIpAddresses++; } // lock this if it is used on other threads

    if (e.Reply.Status == IPStatus.Success)
    {
        //Do something
    }
    else
    {
        /*Computer is down*/
    }
    lock (SyncRoot) { // lock this to ensure reading the right value of sentPings
        //This checks if sent equals recieved
        if (recievedIpAddresses == sentPings )
        {
            //All returned
        }
    }
}

上記のサンプルは、異なる CPU コアが異なる値を読み取らないように、共有メモリからの読み取りと書き込みを強制します。ただし、コードによっては、最初のループが と の両方sentPingspingCounter1 つlockの で保護し、2 番目のメソッドでさえlock.

lockパフォーマンスの問題が発生するため、使用しないでください。ロックフリーは非常にトレンディです。要するにlock、ほとんどの場合、他の選択肢よりも単純です。競合状態も発生する可能性があるため、上記のサンプルよりもロックを粗くする必要がある場合があります。プログラム全体を見ずに、より良いサンプルを提供することは困難です。

Interlocked.Increment

ここで使用する主な理由lockは、各読み取りと書き込みを強制的に CPU キャッシュではなくメモリから取得するためです。したがって、一貫した値を取得する必要があります。ロックの代わりにInterlocked.Incrementを使用することもできますが、それを 2 つの個別の変数で使用する場合は、競合状態を注意深く監視する必要があります。

レース条件

(編集)

ロックしても、問題が発生する可能性があります。13 のターゲット アドレスについては、このタイムラインをご覧ください (不運な場合もあります)。この理由がよくわからない場合は、「Managed Threading Basics」「Threading in C# - Joseph Albahari」をご覧ください。

  • T1: 1回
    • T1: ping が送信されました
    • T1:sentPings++
  • T2: 1回
    • recievedIpAddresses++;
    • T2: その他
  • その間 T1: 12 回
    • T1: ping が送信されました
    • T1: sentPings++(現在は 13 に等しい)
  • T2:recievedIpAddresses == sentPingsテスト - 等しくないため失敗するようになりました
  • T3~T14:入りpingCompletedますrecievedIpAddresses++;
  • T1 が終了し、他の 12 のスレッドがバックグラウンドで戻る前に、アプリケーションは ping カウントを書き出す (またはさらに悪いことに完全に終了する) ようになります。

コード内のこの種の競合状態を注意深く監視し、それに応じて調整する必要があります。スレッドに関するすべてのことは、それらが操作をオーバーラップすることです。

同期ルート

脚注:

SyncRootが次のように宣言されている理由: private readonly object SyncRoot = new object();?

  • クラス フィールドを保護するためのクラス フィールドです。staticコンソール アプリを使用している場合は、 static. ただし、staticクラスで使用すると、すべてのインスタンスが同じオブジェクトをロックするため、競合が発生します
  • readonly意図を宣言し、あなた (または他のチーム メンバー) が後でそれを上書きするのを防ぐためです。
  • それは次のobjectとおりです。
    • オブジェクト以外は必要ありません
    • 値型をロックすることはできません
    • クラス インスタンスをロックしないでください (より複雑なコードでのデッドロックを防ぐため)。
    • 公開しないでください(デッドロックを防ぐためにも)
  • このステートメントによって、クラスとともに (スレッドセーフな方法で) インスタンス化されます。
  • SyncRootこれは例として呼び出されます。Visual Studio は歴史的に、そのスニペットでそれを呼び出してきました
于 2013-05-31T09:17:30.890 に答える