【问题标题】:ConcurrentQueue .Net: Multithreaded consumerConcurrentQueue .Net:多线程消费者
【发布时间】:2016-08-29 06:37:33
【问题描述】:

我有一个非常基本的问题,更多的是关于ConcurrentQueue 的概念。队列是先进先出的。当多个线程开始访问它时,我们如何保证 FIFO? 假设,我添加了AppleOrangesLemonPeachApricot - 按此顺序。第一个TryTake 应该返回Apple。但是当多个线程开始发出自己的TryTake 请求时会发生什么?当一个线程可以返回Lemon 甚至在另一个线程可以返回Apple 之前,是否有可能?我假设其他项目也将被退回,直到队列为空。但这些回报是否会围绕先进先出的基本原则?

【问题讨论】:

    标签: c# .net multithreading concurrent-queue


    【解决方案1】:

    ConcurrentQueue 本身的行为总是 FIFO。

    当我们谈论线程“返回”来自ConcurrentQueue 的项目时,我们谈论的操作涉及将项目出列执行某种操作,使您能够观察什么已经出队。无论是打印输出还是将该项目添加到另一个列表,您实际上都不知道哪个项目已从队列中取出,直到您检查它。

    虽然队列本身是 FIFO,但您无法预测其他事件(例如检查出队项目)的发生顺序。这些项目将以先进先出的方式出列,但您可能会也可能无法观察到按该顺序从队列中出来的内容。不同的线程可能不会按照它们从队列中删除项目的完全相同的顺序执行检查或输出。

    换句话说,它会发生 FIFO,但它可能会或可能不会总是看起来像它。如果处理项目的确切顺序很关键,您不会希望同时读取 ConcurrentQueue

    如果您要对此进行测试(我将要写一些东西),那么您可能会发现大部分时间项目都以精确的 FIFO 顺序进行处理,但有时它们不会如此。


    这是一个控制台应用程序。会的

    • 将 1-5000 之间的数字插入到 ConcurrentQueue 中,单线程。
    • 执行并发操作以使每个项目出列并将它们移动到另一个ConcurrentQueue这是“多线程消费者”。
    • 读取第二个队列中的项目(同样是单线程)并报告任何乱序的数字。

    我运行它很多次,没有什么是乱序的。但大约 50% 的情况下,它只报告了一些乱序的数字。因此,如果您指望所有数字都按原始顺序进行处理,那么大多数情况下几乎所有数字都会发生这种情况。但它不会。如果您不关心确切的顺序,那很好,但如果您这样做,则会出现错误且不可预测。

    结论 - 不要依赖于多线程操作的确切顺序。

    using System;
    using System.Collections.Concurrent;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace ConcurrentQueueExperiment
    {
        class Program
        {
            static void Main(string[] args)
            {
                var inputQueue = new ConcurrentQueue<int>();
                var outputQueue = new ConcurrentQueue<int>();
                Enumerable.Range(1,5000).ToList().ForEach(inputQueue.Enqueue);
                while (inputQueue.Any())
                {
                    Task.Factory.StartNew(() =>
                    {
                        int dequeued;
                        if (inputQueue.TryDequeue(out dequeued))
                        {
                            outputQueue.Enqueue(dequeued);
                        }
                    });
                }
                int output = 0;
                var previous = 0;
                while (outputQueue.TryDequeue(out output))
                {
                    if(output!=previous+1)
                        Console.WriteLine("Out of sequence: {0}, {1}", previous, output);
                    previous = output;
                }
                Console.WriteLine("Done!");
                Console.ReadLine();
            }
        }
    }
    

    【讨论】:

    • +1,特别是对于“这些项目将按 FIFO 出列,但您可能会也可能无法观察到按该顺序从队列中出来的内容。”
    • 非常感谢。这肯定有助于回答。但是,如果它总是先进先出,那么多线程有什么帮助呢?如果多线程也可以“报告”这些项目,我还不如依次TryTake?如果多线程不能同时取出任何项目而只需要取出第一个项目,那么多线程的任何好处(我知道这违背了队列的目的,但肯定多线程和顺序出队不会齐头并进?)
    • 我刚刚检查了 Scott 的代码并删除了对 dequeue 的 TPL 调用并使其成为顺序。有巨大的性能节省。在使用 TPL 时,在 i7、四核、16 GB 机器中,50K 项的出列需要近 1000 毫秒,如果我按顺序执行出列,我平均在 23-30 毫秒内得到它。并发队列的多线程也是一个瓶颈,因为这些项目无论如何都会被FIFO检索。这又回到了我寻求帮助的上一个问题——多线程对并发队列的出队操作有什么好处?
    • 多线程在出队操作上可能有也可能没有任何优势。我永远不会推荐多线程,除非你知道你需要它或者无法避免它。 ConcurrentQueue 的意义在于,如果需要,您可以可以。练习的重点只是展示多线程应用程序的行为方式。主要的一点是,当事件的确切顺序很重要时,我们不应该启动并行或多线程行为。或者更广泛地说,表明它的行为是可以预见的不一致。
    猜你喜欢
    • 1970-01-01
    • 2017-02-01
    • 1970-01-01
    • 2015-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-13
    相关资源
    最近更新 更多