我有一个非常基本的问题,更多的是关于ConcurrentQueue
. 队列是先进先出的。当多个线程开始访问它时,我们如何保证 FIFO?假设,我按顺序添加了Apple
、Oranges
、Lemon
和Peach
- Apricot
。第一个TryTake
应该返回Apple
。但是当多个线程开始发出自己的TryTake
请求时会发生什么?当一个线程Lemon
甚至在另一个线程可以返回之前返回时,是否有可能Apple
?我假设其他项目也将被退回,直到队列为空。但是这些回报会围绕先进先出的基本原则进行吗?
1 回答
自身的行为ConcurrentQueue
总是先进先出。
当我们谈论线程从 中“返回”项目时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();
}
}
}