ConcurrentQueue <t>或Queue <t>当一个线程只排队并且另一个线程只出列队列

时间:2018-04-29 11:28:49

标签: c# multithreading queue fifo

我有一个FIFO和两个线程。一个线程只会入队到FIFO,而另一个线程只会从FIFO中出列。我需要使用ConcurrentQueue还是足够的队列?

2 个答案:

答案 0 :(得分:2)

如果您有多个线程,则需要对Queue对象实例进行线程安全和同步。为了避免重新发明轮子并自己动手,我建议使用微软的ConcurrentQueue

MSDN: https://docs.microsoft.com/en-us/dotnet/api/system.collections.queue?view=netframework-4.7.1

  

如果需要访问,请使用ConcurrentQueue或ConcurrentStack   同时从多个线程收集。

如果您使用多个线程更新对象实例的Queue(即不是ConcurrentQueue),则可能会遇到运行时异常,例如:

  • ArgumentOutOfRangeException
  • ArgumentException(InvalidOffLen)
  • ExceptionResource.InvalidOperation_EmptyQueue

如果由于CPU线程调度而正在修改Queue的内部状态但尚未完成,则可能存在异常。如果另一个线程在处于不一致状态时访问Queue对象实例,则可能并且可能会遇到这些异常。

要审核的源代码:

.Net Framework 4.7.1 https://referencesource.microsoft.com/#System/compmod/system/collections/generic/queue.cs

示例控制台应用程序:

运行以下作为您的实验室,您应该遇到System.InvalidOperationException

System.InvalidOperationException:'在枚举器实例化后修改了集合。'

class Program
{
    static Queue<string> Queue = new Queue<string>();

    static void Main(string[] args)
    {
        Thread producer = new Thread(Enqueue);
        Thread consumer = new Thread(Dequeue);

        producer.Start();
        consumer.Start();

        Console.ReadKey();
    }

    static void Enqueue()
    {
        for (int i = 0; i < 10000; i++)
        {
            Queue.Enqueue("Number : " + i);
            SimulateWork();
        }
    }

    static void Dequeue()
    {
        while (true)
        {
            if (Queue.Any())
            {
                Console.WriteLine(Queue.Dequeue());
                SimulateWork();
            }
        }
    }

    static void SimulateWork()
    {
        for (int i = 0; i < 1000000; i++)
        { }
    }
}

本实验演示了在处于不一致状态时访问Queue实例时可能发生的情况。即使只有一个生产者和一个消费者,您也需要适当的同步。

如果您在EnqueueDequeue操作周围添加锁定或同步,您会发现它运行没有问题。

            lock (lockObject)
            {
                Queue.Enqueue("Number : " + i);
                SimulateWork();
            }


            lock (lockObject)
            {
                if (Queue.Any())
                {
                    Console.WriteLine(Queue.Dequeue());
                    SimulateWork();
                }
            }

话虽如此,我建议您手动添加将阻止的锁。这是一个实验练习,可以帮助您理解为什么

微软花了很多时间使用细粒度锁定和无锁机制为ConcurrentQueue提供线程安全集合。

  

某些并发集合类型使用轻量级   同步机制,如SpinLock,SpinWait,SemaphoreSlim,   和CountdownEvent,它们是.NET Framework 4中的新增功能   同步类型通常在短时间内使用忙碌旋转   在他们将线程置于真正的等待状态之前。等待时间   预计非常短,旋转的计算量要小得多   比等待昂贵,这涉及昂贵的内核过渡。   对于使用旋转的集合类,这种效率意味着   多个线程可以以非常高的速率添加和删除项目。对于   有关旋转与阻塞的更多信息,请参阅SpinLock和   SpinWait。

如上所述,如果您有多个线程,则需要线程安全和同步Queue对象实例。为了避免重新发明轮子并自己动手,我会高度建议使用Microsoft的ConcurrentQueue

参考文献:

线程安全集合:

https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/

锁定关键字

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/lock-statement

线程:

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/threading/index

答案 1 :(得分:2)

简短回答:是的,你仍然需要一个线程安全的解决方案 - 即使只有一个编写者线程和一个读者线程。

使用ConcurrentQueue会更容易。如果你愿意,可以使用Queue,但你必须自己进行锁定。