程序需要在1秒钟内ping千多台机器,然后将结果写入日志…由于网络问题,会出现有些机器ping不同,用循环来做就会阻塞,于是选了用异步Ping.SendAsync来做。这个代码其实工作的挺好。然而,程序顺利工作半天以后,我被公司网管部门盯上了,原因是部署了这个程序的服务器,偶尔会CPU上到100%(大约5~10分钟一次)。而常态下,这个程序的CPU使用是在20%左右(其实就是两个线程,一个用来产生异步ping,一个用来处理队列中的日志)
我抓了dump,结合代码分析了一下原因。某些时候,这些异步任务的回调PingCompleted会集中在一个时间点同时结束,这个时候,回调的线程大约有80多个,同时执行时CPU会瞬间飚升到100%。
这种情况下,有什么思路,能避免CPU瞬间飚升到100%,而让cpu能够稳定在20%?
代码大致如下:
public void PingList() { foreach (var ip in _ipList) { try { PingAsnc(ip); } catch { } } } static byte[] data = Encoding.ASCII.GetBytes("12345678901234567890123456789012"); public void PingAsnc(string ip) { Ping ping = new Ping(); PingOptions options = new PingOptions(); options.DontFragment = true; int timeout = 20; ping.PingCompleted += PingCompleted; ping.SendAsync(ping.IP, timeout, data, options, null); } public void PingCompleted(object sender, PingCompletedEventArgs e) { PingReply reply = e.Reply; //将reply加入一个无锁队列,用以写日志balabalabala。 }
解决方案:50分
解决方案:10分
PingCompleted里边的方法用Control.Invoke搞到主线程去做,这样永远只用一个核,在4核CPU上占用率就是25%再或者限制同时ping的数量,比如100个:
Task.Factory.StartNew(() =>
{
_ipList.AsParallel().WithDegreeOfParallelism(100).ForAll((i) =>
{
var ping = new Ping();
//xxxx
});
});
Task.Factory.StartNew(() =>
{
_ipList.AsParallel().WithDegreeOfParallelism(100).ForAll((i) =>
{
var ping = new Ping();
//xxxx
});
});
解决方案:40分
Thread.Sleep(0)在这种需求下可能不合适,你最好是在PingCompleted中处理队列的地方使用lock串行处理。