ConcurrentQueue<T> 或 Queue<T> 当一个线程只入队而另一个线程只出队时
ConcurrentQueue<T> or Queue<T> when one thread only ever enqueues and another thread only ever dequeues
我有一个 FIFO 和两个线程。一个线程永远只会排队到 FIFO,而另一个线程永远只会从 FIFO 出队。我需要使用 ConcurrentQueue 还是 Queue 够用?
如果您有多个线程,则 Queue
对象实例需要线程安全和同步。为了避免重新发明轮子和自己动手,我建议使用微软的ConcurrentQueue
。
MSDN:
https://docs.microsoft.com/en-us/dotnet/api/system.collections.queue?view=netframework-4.7.1
Use ConcurrentQueue or ConcurrentStack if you need to access the
collection from multiple threads concurrently.
如果您使用 Queue
(即不是 ConcurrentQueue
)和多个线程更新对象实例,您可能会遇到 运行 时间异常,例如:
- ArgumentOutOfRangeException
- ArgumentException (InvalidOffLen)
- ExceptionResource.InvalidOperation_EmptyQueue
如果队列的内部状态正在修改但由于 CPU 线程调度尚未完成,则可能会出现异常。如果另一个线程在不一致状态下访问 Queue 对象实例,您可能并且很可能会遇到这些异常。
要查看的源代码:
.Net Framework 4.7.1
https://referencesource.microsoft.com/#System/compmod/system/collections/generic/queue.cs
示例控制台应用程序:
运行以下作为你的实验室,你应该会遇到一个System.InvalidOperationException
System.InvalidOperationException: 'Collection was modified after the enumerator was instantiated.'
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
实例时会发生什么情况。即使只有一个生产者和一个消费者,您也需要适当的同步。
如果您在 Enqueue
和 Dequeue
操作周围添加锁定或同步,您会注意到 运行s 没有问题。
lock (lockObject)
{
Queue.Enqueue("Number : " + i);
SimulateWork();
}
lock (lockObject)
{
if (Queue.Any())
{
Console.WriteLine(Queue.Dequeue());
SimulateWork();
}
}
话虽如此,我 不 建议您手动添加将阻塞的锁。这更像是一个实验练习,可以帮助您理解 WHY.
Microsoft 投入了大量时间为我们提供线程安全集合,例如使用细粒度锁定和无锁机制的 ConcurrentQueue
。
Some of the concurrent collection types use lightweight
synchronization mechanisms such as SpinLock, SpinWait, SemaphoreSlim,
and CountdownEvent, which are new in the .NET Framework 4. These
synchronization types typically use busy spinning for brief periods
before they put the thread into a true Wait state. When wait times are
expected to be very short, spinning is far less computationally
expensive than waiting, which involves an expensive kernel transition.
For collection classes that use spinning, this efficiency means that
multiple threads can add and remove items at a very high rate. For
more information about spinning vs. blocking, see SpinLock and
SpinWait.
如上所述,如果您有多个线程,则 Queue
对象实例需要线程安全和同步。为了避免自己重新发明轮子,我强烈建议使用微软的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
简短回答:是的,您仍然需要一个线程安全的解决方案——即使只有一个编写器线程和一个 reader 线程。
使用ConcurrentQueue会更容易。如果您愿意,可以改用 Queue,但您必须自己进行锁定。
我有一个 FIFO 和两个线程。一个线程永远只会排队到 FIFO,而另一个线程永远只会从 FIFO 出队。我需要使用 ConcurrentQueue 还是 Queue 够用?
如果您有多个线程,则 Queue
对象实例需要线程安全和同步。为了避免重新发明轮子和自己动手,我建议使用微软的ConcurrentQueue
。
MSDN: https://docs.microsoft.com/en-us/dotnet/api/system.collections.queue?view=netframework-4.7.1
Use ConcurrentQueue or ConcurrentStack if you need to access the collection from multiple threads concurrently.
如果您使用 Queue
(即不是 ConcurrentQueue
)和多个线程更新对象实例,您可能会遇到 运行 时间异常,例如:
- ArgumentOutOfRangeException
- ArgumentException (InvalidOffLen)
- ExceptionResource.InvalidOperation_EmptyQueue
如果队列的内部状态正在修改但由于 CPU 线程调度尚未完成,则可能会出现异常。如果另一个线程在不一致状态下访问 Queue 对象实例,您可能并且很可能会遇到这些异常。
要查看的源代码:
.Net Framework 4.7.1 https://referencesource.microsoft.com/#System/compmod/system/collections/generic/queue.cs
示例控制台应用程序:
运行以下作为你的实验室,你应该会遇到一个System.InvalidOperationException
System.InvalidOperationException: 'Collection was modified after the enumerator was instantiated.'
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
实例时会发生什么情况。即使只有一个生产者和一个消费者,您也需要适当的同步。
如果您在 Enqueue
和 Dequeue
操作周围添加锁定或同步,您会注意到 运行s 没有问题。
lock (lockObject)
{
Queue.Enqueue("Number : " + i);
SimulateWork();
}
lock (lockObject)
{
if (Queue.Any())
{
Console.WriteLine(Queue.Dequeue());
SimulateWork();
}
}
话虽如此,我 不 建议您手动添加将阻塞的锁。这更像是一个实验练习,可以帮助您理解 WHY.
Microsoft 投入了大量时间为我们提供线程安全集合,例如使用细粒度锁定和无锁机制的 ConcurrentQueue
。
Some of the concurrent collection types use lightweight synchronization mechanisms such as SpinLock, SpinWait, SemaphoreSlim, and CountdownEvent, which are new in the .NET Framework 4. These synchronization types typically use busy spinning for brief periods before they put the thread into a true Wait state. When wait times are expected to be very short, spinning is far less computationally expensive than waiting, which involves an expensive kernel transition. For collection classes that use spinning, this efficiency means that multiple threads can add and remove items at a very high rate. For more information about spinning vs. blocking, see SpinLock and SpinWait.
如上所述,如果您有多个线程,则 Queue
对象实例需要线程安全和同步。为了避免自己重新发明轮子,我强烈建议使用微软的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
简短回答:是的,您仍然需要一个线程安全的解决方案——即使只有一个编写器线程和一个 reader 线程。
使用ConcurrentQueue会更容易。如果您愿意,可以改用 Queue,但您必须自己进行锁定。