Code Bye

大量异步方法产生大量线程,如何压低CPU消耗?

程序需要在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分
这不是很正常的事情吗?
要CPU稳定,只能使用单线程/串行处理。
另外如果PingCompleted仅仅是一个队列操作,出现CPU 100%的概率应该是相当低的,80个回调线程确实有点不正常。
另外可以试试在PingAsnc(ip)后Thread.Sleep(0),看看能不能降低回调并发。
解决方案:10分
PingCompleted里边的方法用Control.Invoke搞到主线程去做,这样永远只用一个核,在4核CPU上占用率就是25%再或者限制同时ping的数量,比如100个:
Task.Factory.StartNew(() =>
{
_ipList.AsParallel().WithDegreeOfParallelism(100).ForAll((i) =>
{
var ping = new Ping();
//xxxx
});
});
解决方案:40分
Thread.Sleep(0)在这种需求下可能不合适,你最好是在PingCompleted中处理队列的地方使用lock串行处理。

CodeBye 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明大量异步方法产生大量线程,如何压低CPU消耗?