多线程c#应用程序达到高CPU使用率

Pur*_*tha 0 .net c# multithreading

我正在开发一个连接到x个硬件设备的应用程序.我设计了ReaderLayer,以便有一个专用线程,代码运行以连接到单个设备并连续读取设备数据缓冲区.阅读器层的代码如下:

        while (true)
        {

                // read buffer from the reader
                IList<IRampActiveTag> rampTagList = ConnectedReader.ReadBuffer();
                if (rampTagList != null && rampTagList.Any())
                {
                    // trigger read event handler
                    if (ReadMessage != null)
                        ReadMessage(this, new RampTagReadEventArg(rampTagList));
                }


        }
Run Code Online (Sandbox Code Playgroud)

在读取器层之上构建了一个逻辑层,负责处理从读取器接收的数据并通过HTTP Post转发它.它有多个线程,每个线程运行一个单独的逻辑块,必须处理写入其线程安全队列的相关信息.逻辑层订阅ReadForward由Reader Layer公开的事件,并通过写入它将该数据转发到相关的逻辑块ConcurrentQueue.

每个Logic Block中的代码非常简单:

        public void ProcessLogicBuffer()
        {

            while (true)
            {   
                // Dequeue the list
                IRampActiveTag tag;
                LogicBuffer.TryDequeue(out tag);
                if (tag != null)
                {

                    ProcessNewTagReceivedLogic(tag);
                    Console.WriteLine("Process Buffer #Tag {0}. Buffer Count #{1}", tag.NewLoopId, LogicBuffer.Count);
                }

            }
        }
Run Code Online (Sandbox Code Playgroud)

读取器层和逻辑层的循环布局大致相同while(true).然而,当我测试3个读卡器和3个逻辑块时,我发现我的CPU利用率上升到77%.我很快将CPU使用率缩小到Logic Threads,因为我获得了2%的使用率和1个块的25%使用率.

只需在逻辑线程中添加3个块的Thread.Sleep(100),我就可以将CPU使用率降低到~3%,但是,我担心我的逻辑可能不同步.通过查看样本,任何人都可以向我建议任何改进,因为生产代码需要使用大约30个逻辑块.我需要改变我的架构吗?

Jim*_*hel 5

你在这个循环中做了很多不必要的轮询:

    public void ProcessLogicBuffer()
    {

        while (true)
        {   
            // Dequeue the list
            IRampActiveTag tag;
            LogicBuffer.TryDequeue(out tag);
            if (tag != null)
            {

                ProcessNewTagReceivedLogic(tag);
                Console.WriteLine("Process Buffer #Tag {0}. Buffer Count #{1}", tag.NewLoopId, LogicBuffer.Count);
            }

        }
    }
Run Code Online (Sandbox Code Playgroud)

假设大多数时间队列都是空的,这段代码所做的大部分工作都是重复检查队列."我有什么事吗?现在怎么样?现在?......"

您可以通过替换您摆脱所有无用的轮询ConcurrentQueueBlockingCollection.假设您更改LogicBuffer为a BlockingCollection,则循环如下所示:

    public void ProcessLogicBuffer()
    {
        foreach (var tag in LogicBuffer.GetConsumingEnumerable())
        {
            ProcessNewTagReceivedLogic(tag);
            Console.WriteLine("Process Buffer #Tag {0}. Buffer Count #{1}", tag.NewLoopId, LogicBuffer.Count);
        }
    }
Run Code Online (Sandbox Code Playgroud)

GetConsumingEnumerable将在项目到达时将项目出列,并将继续这样做,直到该项目为空并且已标记为完成添加.请参见BlockingCollection.CompleteAdding.然而,真正的美丽GetConsumingEnumerable是非忙碌的等待.如果集合为空,则等待项目可用的通知.它没有做很多无用的轮询TryDequeue.

更改使用代码ConcurrentQueue,以便它使用BlockingCollection的却是很容易的.你可以在一小时内完成.这样做会使您的代码更简单,并且它将消除不必要的轮询.

更新

如果您需要进行一些定期处理,您有两种选择.如果你想在读取的同一循环中完成BlockingCollection,那么你可以将使用的循环更改为GetConsumingEnumerable:

Stopwatch sw = Stopwatch.StartNew();
TimeSpan lastProcessTime = TimeSpan.Zero;
while (true)
{
    IRampActiveTag tag;
    // wait up to 200 ms to dequeue an item.
    if (LogicBuffer.TryTake(out tag, 200))
    {
        // process here
    }
    // see if it's been 200 ms or more
    if ((sw.ElapsedMilliseconds - lastProcessTime.TotalMilliseconds) > 200)
    {
        // do periodic processing
        lastProcessTime = sw.Elapsed;
    }
}
Run Code Online (Sandbox Code Playgroud)

这将为您提供200至400毫秒的定期处理速率.在我看来,它有点难看,而且对你的目的来说可能不够好.您可以将超时减少到20毫秒而不是200毫秒,这将使您更接近200毫秒的轮询速率,代价是10倍的呼叫TryTake.可能,您不会注意到CPU使用率的差异.

我倾向于将该周期性处理循环与队列使用者分开.创建一个每200毫秒触发一次的计时器,让它完成工作.例如:

public void ProcessLogicBuffer()
{
    var timer = new System.Threading.Timer(MyTimerProc, null, 200, 200);

    // queue processing stuff here

    // Just to make sure that the timer isn't garbage collected. . .
    GC.KeepAlive(timer);
}

private void MyTimerProc(object state)
{
    // do periodic processing here
}
Run Code Online (Sandbox Code Playgroud)

这将使您的更新频率非常接近200毫秒.定时器proc将在一个单独的线程上执行,为true,但是线程处于活动状态的唯一时间是定时器触发时.

  • 要进行更新,请注意您使用的计时器.`System.Timers.Timer`将如何使用它,但如果您使用`System.Threading.Timer` [它可能会收集垃圾](http://stackoverflow.com/questions/18136735/can-timers -get-automatic-garbage-collecting)如果你的程序中没有对它的引用.(例如,ProcessLogicBuffer末尾的`GC.KeepAlive(timer)`会为发布的示例修复它,如果示例使用的是可以处理的计时器) (2认同)