如何修复BlockingCollection / ConcurrentQueue消耗延迟?

时间:2014-02-05 12:14:39

标签: .net multithreading blockingcollection

在启用了超线程的Windows7 quadcore上,我有一百个线程通过BlockingCollection<T>进行通信(所有线程都使用默认构造函数初始化,因此在内部使用ConcurrentQueue<T>

所有主题每秒收到10-100封邮件,但每天只收到4-20条消息

我的问题与最后一个消费者有关:大部分时间,它被阻止等待新消息,但是当消息准备好被消费时,应该尽快处理它,可能是即时的。

问题在于,当消息被添加到专用于此消费者的BlockingCollection时,会在几秒钟后收到消息(Take()在消息入队后3到12秒后返回)。 / p>

我猜这个问题与windows如何安排这个帖子有关。

我试图在没有任何改进的情况下增加此消费者的ThreadPriority。 然后我尝试将其处理器亲和性设置为专用核心,并且我已经改变了所有其他线程的亲和力以使用其他核心,但仍然没有任何改进。

我该如何解决这个问题?什么是实际问题?

这是线程循环(commandsBlockingCollection):

while (!commands.IsCompleted)
{
    _log.Debug("Waiting for command.");

    command = commands.Take();

    _log.DebugFormat("Received: {0}", command);

    command.ApplyTo(target);

    _log.InfoFormat("Dispatched: {0}", command);
}

我应该直接使用ConcurrentQueue,在没有待处理的消息时,睡眠时间为50毫秒(这是可接受的延迟)吗?

请注意
没有CPU使用超过50%。

至于延迟:日志显示(大多数时间)在“Dispatched:...”和“Waiting for command”之间经过几秒钟。 (又名commands.IsCompleted)和其他一些“等待命令”。和“收到:......”(又名commands.Take()

所有“理智”的线程都是I / O绑定但他们合作:相信我,我无法改变他们的设计。然而它们的效果相当不错:唯一不足的是低负载线程,它可以完成不同类型的工作。我知道thread priority are evil,但我找不到更好的解决方案。

(几乎)复制问题的测试
以下是在服务器上几乎复制问题的测试。 “几乎”因为测试压力CPU(全部达到100%),而在实际应用中所有CPU都低于50%:

public class ThreadTest
{
    class TimedInt
    {
        public int Value;

        public DateTime Time;
    }
    [Test]
    public void BlockingCollection_consumedWithLowLoad_delaySomeTimes()
    {
        // arrange:
        int toComplete = 0;
        BlockingCollection<KeyValuePair<DateTime, TimedInt>> results = new BlockingCollection<KeyValuePair<DateTime, TimedInt>>();
        BlockingCollection<TimedInt> queue = new BlockingCollection<TimedInt>();
        Action<int> producer = a =>
        {
            int i = 1;
            int x = Convert.ToInt32(Math.Pow(a, 7));
            while (i < 200000000)
            {
                if (i % x == 0)
                {
                    queue.Add(new TimedInt { Time = DateTime.Now, Value = i });
                    Thread.SpinWait(100); // just to simulate a bit of actual work here
                    queue.Add(new TimedInt { Time = DateTime.Now, Value = i + 1 });
                }
                i++;
            }
            Interlocked.Decrement(ref toComplete);
        };
        Action consumer = () =>
        {
            Thread.CurrentThread.Priority = ThreadPriority.Highest; // Note that the consumer has an higher priority
            Thread.CurrentThread.Name = "Consumer";
            while (toComplete > 0)
            {
                TimedInt v;
                if (queue.TryTake(out v, 1000))
                {
                    DateTime now = DateTime.Now;
                    results.Add(new KeyValuePair<DateTime, TimedInt>(now, v));
                }
            }
        };

        // act:
        List<Thread> threads = new List<Thread>();
        threads.Add(new Thread(new ThreadStart(consumer)));
        for (int i = 0; i < 200; i++)
        {
            var t = new Thread(new ThreadStart(() => producer(7 + (i % 3))));
            t.Name = "Producer " + i.ToString();
            threads.Add(t);
            toComplete++;
        }
        threads.ForEach(t => t.Start());
        threads.ForEach(t => t.Join());

        // assert:
        Assert.AreEqual(0, results.Where(kvp => (kvp.Key - kvp.Value.Time).TotalMilliseconds > 1000).Count());
    }
}

有什么想法吗?

0 个答案:

没有答案