【问题标题】:Poor performance with Concurrent Queue并发队列性能不佳
【发布时间】:2018-08-01 19:56:59
【问题描述】:

我有两个线程。一种是从串行端口收集数据并将其放入一个数组中,然后将其添加到并发队列中。另一个线程正在获取这些数组并实时绘制数据。每秒大约有 150 个 50 字节的数据包。 我遇到的问题是,当它运行时,它会消耗大约 10% 的 CPU。这是在非常快的核心 I7 Haswell 上。如果我在消费线程中添加 Thread.Sleep(1),那么 CPU 利用率会下降到 1%。问题是如果我把它放在 Thread.Sleep(2) 上,那么数据包就不再同步了。所以这不是一个解决方案,如果它在较慢的计算机上运行,​​它可能无法正常工作。

代码很简单。这是生产者线程:

    static void FillQueue(byte[] buffer)
    {
        dataQueue.Enqueue(buffer);
    }

这里是消费者线程:

        while (continuePolling)
        {                
            Thread.Sleep(1); //if removed, there is 10% CPU utilization.  If higher then packet synchronization is lost.

            if (dataQueue.TryDequeue(out result))
            {
                ProcessPacket(result);
            }               
        }

我觉得这很难理解,因为 ProcessPacket 方法执行大约需要 0.3 毫秒,并且每秒调用大约 10 次。

我现在尝试改用阻塞集合。 这里是制作人

 BlockingCollection<byte[]> dataQueue = new BlockingCollection<byte[]>;

 public static void addData(buffer)
 {
      dataQueue.add(buffer);
 }

这里是消费者

 while (dataQueue.TryTake(out result)
 {
     ProcessPacket(result);
 }

根本没有区别!它使用 10% 的 CPU,如果我添加一个 Thread.Sleep(1) 就可以了,但是一个 Thread.Sleep(2) 我会丢失数据包 我不明白。每个数据包有 50 个字节。而已。它们的消耗速度与生产速度一样快。 谢谢

【问题讨论】:

  • 听起来您没有在串行端口上使用异步 I/O。向我们展示 I/O 代码
  • 考虑使用BlockingCollection 而不是ConcurrentQueue 以避免需要Thread.Sleep 调用(使用阻塞Take 方法)。
  • @MickyD 串行 I/O 花费的时间不到一毫秒,并且该线程不是瓶颈。如果我注释掉消费者,那么就没有性能问题。

标签: c# .net multithreading concurrency queue


【解决方案1】:

那是因为你基本上有while (true) {} 循环。 ProcessPacket 耗时 0.3ms,每秒有 10 个数据包,所以每秒有用工作 3ms。剩下的 997 毫秒,你的循环不断检查队列中是否有新项目,浪费你的 CPU 资源。

相反,将BlockingCollection 与您的队列一起使用,这为您的任务提供了更好的选择。它支持阻塞出队(必要时带有超时和取消令牌),因此不会浪费 CPU:

var dataQueue = new BlockingCollection<string>(new ConcurrentQueue<string>());
// Add instead of Enqueue
dataQueue.Add("some item");
// Take instead of Dequeue, this will block until item is available in queue
var result = dataQueue.Take();
// blocks until item is available or timeout happens
if (dataQueue.TryTake(out result, TimeSpan.FromMilliseconds(100)))

它还支持“流”接口,所以你的代码可以是:

foreach (var result in dataQueue.GetConsumingEnumerable()) {
    ProcessPacket(result);        
}

而不是continuePolling 标志 - 调用

dataQueue.CompleteAdding();

当没有更多物品需要时(因此,否则您会将 continuePolling 设置为 false) - 这将使 GetConsumingEnumerable 完成提供物品并返回。

【讨论】:

  • 您好,我刚刚用 BlockingCollection 更新了问题。我在那里也做错了吗?这是一个实时过程,将继续获取和处理数据包,直到应用程序关闭。因此, continuePolling 需要在那里。
  • @Tom 是的 - 您正在使用 TryTake 没有超时,这与您的旧代码完全相同 - 这会浪费 CPU(如果队列中没有项目,它会立即返回)。如果您不关心超时,请致电var result = dataQueue.Take()(就像我的回答一样)。
  • 就是这样!!!没有线程休眠的 1% CPU。非常感谢,今晚我可以睡个好觉了!
猜你喜欢
  • 2017-06-12
  • 1970-01-01
  • 2017-06-27
  • 2020-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-18
  • 1970-01-01
相关资源
最近更新 更多