应该';如果不使用锁,这不会失败吗?简单的生产者-消费者

本文关键字:失败 简单 消费者 生产者 如果不 应该 | 更新日期: 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-"枚举器实例化后修改了集合。"这意味着您在使用多个线程时修改数据。

您可以在每次EnqueueDequeue时使用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);
        }
    }
}