应该';如果不使用锁,这不会失败吗?简单的生产者-消费者
本文关键字:失败 简单 消费者 生产者 如果不 应该 | 更新日期: 2023-09-27 17:58:06
我有一个队列,一个包含生产者线程的列表和一个包含消费者线程的列表。
我的代码看起来像这个
public class Runner
{
List<Thread> Producers;
List<Thread> Consumers;
Queue<int> queue;
Random random;
public Runner()
{
Producers = new List<Thread>();
Consumers = new List<Thread>();
for (int i = 0; i < 2; i++)
{
Thread thread = new Thread(Produce);
Producers.Add(thread);
}
for (int i = 0; i < 2; i++)
{
Thread thread = new Thread(Consume);
Consumers.Add(thread);
}
queue = new Queue<int>();
random = new Random();
Producers.ForEach(( thread ) => { thread.Start(); });
Consumers.ForEach(( thread ) => { thread.Start(); });
}
protected void Produce()
{
while (true)
{
int number = random.Next(0, 99);
queue.Enqueue(number);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " Produce: " + number);
}
}
protected void Consume()
{
while (true)
{
if (queue.Any())
{
int number = queue.Dequeue();
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " Consume: " + number);
}
else
{
Console.WriteLine("No items to consume");
}
}
}
}
这种失败难道不应该是缺少lock关键字的悲惨原因吗?它失败过一次,因为它试图在队列为空时出列,使用lock关键字可以解决这个问题,对吗?
如果上面的代码不需要lock关键字,那么什么时候需要呢?
提前谢谢
锁定是为了消除应用程序的异常行为,尤其是在多线程中。最常见的目标是消除导致不确定性程序行为的"竞赛条件"。
这就是你看到的行为。在一次运行中,队列中没有项目时会出现错误,而在另一次运行时则没有问题。这是比赛条件。正确使用锁定将消除这种情况。
使用不带锁的队列确实不是线程安全的。但是,与使用锁相比,您可以尝试ConcurrentQueue。谷歌搜索"C#并发队列",你会发现很多例子,例如,这一个比较了队列与锁和并发队列的使用和性能。
为了澄清现有的答案,如果您有多线程问题(例如竞争条件),则不能保证总是失败-它可能会以非常不可预测的方式失败。
原因是,访问资源的两个(或多个)线程可能会在不同的时间尝试访问资源,确切地说,每个线程尝试访问资源的时间将取决于许多因素(CPU的速度、可用的处理器内核数量、当时运行的其他程序、是运行发布版还是调试版,还是在调试器下运行,等等)。你可以多次运行它而不出现故障,然后突然"莫名其妙"地失败——这会使这些错误极难追踪,因为它们在你编写错误代码时并不经常出现,但在你编写不同的无关代码时更常见。
如果你要使用多线程,你必须仔细阅读这个主题,了解什么时候会出错,以及如何正确处理它——错误地使用锁定可能与根本不使用锁定一样危险(如果不是更危险的话)(锁定可能会导致程序"锁定"时的死锁)。这是aof编程必须小心处理!
是的,此代码将失败。队列需要支持多线程。使用ConcurrentQueue
。看见http://msdn.microsoft.com/en-us/library/dd267265.aspx
通过运行代码,我收到InvalidOperationException-"枚举器实例化后修改了集合。"这意味着您在使用多个线程时修改数据。
您可以在每次Enqueue
或Dequeue
时使用lock
,因为您可以从多个线程修改队列。一个更好的选择是使用ConcurentQueues,因为它是线程安全和无锁的并发收集。它还提供了更好的性能。
是的,您肯定需要同步对Queue
的访问以使其线程安全。但是,你还有另一个问题。没有任何机制可以阻止消费者在循环中疯狂旋转。同步访问Queue
或使用ConcurrentQueue
不会解决该问题。
实现生产者-消费者模式的最简单方法是使用阻塞队列。幸运的是,.NET 4.0提供了BlockingCollection
,尽管它的名称是阻塞队列的实现。
public class Runner
{
private BlockingCollection<int> queue = new BlockingCollection<int>();
private Random random = new Random();
public Runner()
{
for (int i = 0; i < 2; i++)
{
var thread = new Thread(Produce);
thread.Start();
}
for (int i = 0; i < 2; i++)
{
var thread = new Thread(Consume);
thread.Start();
}
}
protected void Produce()
{
while (true)
{
int number = random.Next(0, 99);
queue.Add(number);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " Produce: " + number);
}
}
protected void Consume()
{
while (true)
{
int number = queue.Take();
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " Consume: " + number);
}
}
}