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

尽管如此,满足条件并且应用程序继续进行?我不知道为什么以及如何发生这种情况。应用程序更新两个整数的代码是否执行得很快?是否有一些 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 内核就不会读取不同的值。但是,根据您的代码,您可能需要更粗粒度的锁定,其中第一个循环同时保护sentPingspingCounter在一个lock中,甚至可能第二个方法完全用lock.

人们可以说不要使用lock,因为它会导致性能问题,并且无锁非常流行。大多数时候,底线lock比其他替代方案更简单。您可能需要使锁定比上述示例更粗粒度,因为您也可能存在竞争条件。如果不查看整个程序,很难给出更好的示例。

Interlocked.Increment

在这里使用的主要原因lock是强制每次读取和写入来自内存,而不是 CPU 缓存,因此您应该获得一致的值。锁定的替代方法是使用Interlocked.Increment,但如果您在两个单独的变量上使用它,则需要仔细观察竞争条件。

比赛条件

(编辑)

即使你锁定你也可能有问题。观看 13 个目标地址的时间线(有些人不走运)。如果您对这是为什么不满意,请查看“Managed Threading Basics”“Threading in C# - Joseph Albahari”

  • T1:1次
    • T1:Ping 发送
    • T1:sentPings++
  • T2:1次
    • 收到的IP地址++;
    • 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 回答