C# ConcurrentQueue.Net:多线程使用者

C# ConcurrentQueue.Net:多线程使用者,c#,.net,multithreading,concurrent-queue,C#,.net,Multithreading,Concurrent Queue,我有一个非常基本的问题,更多的是关于ConcurrentQueue的概念。队列是FIFO。当多个线程开始访问它时,我们如何保证FIFO? 假设我按顺序添加了苹果、橙子、柠檬、桃子和杏子。第一个TryTake应该返回Apple。但是当多个线程开始发出它们自己的TryTake请求时会发生什么呢?当一个线程返回Lemon时,甚至在另一个线程返回Apple之前,是否存在这样的可能性?我假设其他项目也将返回,直到队列为空。但这些返回是否会围绕FIFO的基本原则进行管理?ConcurrentQueue本身的

我有一个非常基本的问题,更多的是关于
ConcurrentQueue
的概念。队列是FIFO。当多个线程开始访问它时,我们如何保证FIFO?
假设我按顺序添加了苹果、橙子、柠檬、桃子和杏子。第一个
TryTake
应该返回
Apple
。但是当多个线程开始发出它们自己的
TryTake
请求时会发生什么呢?当一个线程返回
Lemon
时,甚至在另一个线程返回
Apple
之前,是否存在这样的可能性?我假设其他项目也将返回,直到队列为空。但这些返回是否会围绕FIFO的基本原则进行管理?

ConcurrentQueue本身的行为始终是FIFO

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

虽然队列本身是FIFO,但您无法预测其他事件(如检查已退出队列的项目)的发生顺序。这些项目将以先进先出(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();
        }
    }
}
使用系统;
使用System.Collections.Concurrent;
使用System.Linq;
使用System.Threading.Tasks;
命名空间并发队列实验
{
班级计划
{
静态void Main(字符串[]参数)
{
var inputQueue=新的ConcurrentQueue();
var outputQueue=新的ConcurrentQueue();
Enumerable.Range(15000.ToList().ForEach(inputQueue.Enqueue);
while(inputQueue.Any())
{
Task.Factory.StartNew(()=>
{
int退出队列;
if(inputQueue.TryDequeue(out dequeue))
{
outputQueue.Enqueue(已退出队列);
}
});
}
int输出=0;
var-previous=0;
while(outputQueue.TryDequeue(outoutput))
{
如果(输出!=上一个+1)
WriteLine(“顺序错误:{0},{1}”,前一个,输出);
先前=输出;
}
控制台。WriteLine(“完成!”);
Console.ReadLine();
}
}
}

+1,特别是对于“项目将以先进先出的方式退出队列,但您可能无法观察到队列中按该顺序出现的内容。”非常感谢。这肯定有助于回答这个问题。然而,如果它总是FIFO,那么多线程有什么帮助呢?如果多线程也会这样“报告”项目,我还不如按顺序执行
TryTake
?如果多线程不能同时取出任何项目,只需要取出第一个项目(我知道这违背了排队的目的,但多线程和顺序出列肯定不是齐头并进的),那么多线程有什么好处?我只是检查了Scott的代码,删除了对出列的TPL调用,并将其设置为顺序。这大大节省了性能。而使用TPL,在一台i7四核16 GB的机器上,50K个项目的出列时间几乎为1000毫秒,如果我按顺序出列,平均时间为23-30毫秒。并发队列的多线程也是一个瓶颈,因为项目无论如何都会被FIFO检索。这又回到了我寻求帮助的前一个问题-在并发队列的出列操作中使用多线程有什么好处?在出列操作中使用多线程可能有什么好处,也可能没有。除非您知道自己需要或无法避免多线程,否则我永远不会推荐多线程。
ConcurrentQueue
的要点是,如果需要,您可以这样做。这个练习的目的只是展示多线程应用程序的行为。主要的一点是,当事件的确切顺序很重要时,我们不应该启动并行或多线程行为。或者更广泛地说,表明其行为是可预测的不一致的。