在 C# 中实现阻塞队列

Implementing a blocking queue in C#(在 C# 中实现阻塞队列)

本文介绍了在 C# 中实现阻塞队列的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用下面的代码来实现和测试阻塞队列.我通过启动 5 个并发线程(移除器)将项目从队列中拉出来测试队列,如果队列为空则阻塞,并且 1 个并发线程(加法器)间歇性地将项目添加到队列中.但是,如果我让它运行足够长的时间,我会得到一个异常,因为即使队列为空,其中一个移除器线程也会退出等待状态.

I've use the below code to implement and test a blocking queue. I test the queue by starting up 5 concurrent threads (the removers) to pull items off the queue, blocking if the queue is empty and 1 concurrent thread (the adder) to add items to the queue intermitently. However, if I leave it running for long enough I get an exception because one of the remover threads comes out of a waiting state even when the queue is empty.

有谁知道我为什么会得到异常?请注意,我很想知道为什么这与工作解决方案相比不起作用(因为我可以谷歌).

Does anyone know why I get the exception? Note, I'm interested in knowing why this doesn't work as opposed to a working solution (as I can just Google that).

非常感谢您的帮助.

using System;
using System.Threading;
using System.Collections.Generic;

namespace Code
{
    class Queue<T>
    {
        private List<T> q = new List<T>();

        public void Add(T item)
        {
            lock (q)
            {
                q.Add(item);
                if (q.Count == 1)
                {
                    Monitor.Pulse(q);
                }
            }
        }

        public T Remove()
        {
            lock (q)
            {
                if (q.Count == 0)
                {
                    Monitor.Wait(q);
                }
                T item = q[q.Count - 1];
                q.RemoveAt(q.Count - 1);
                return item;
            }
        }
    }

    class Program
    {
        static Random r = new Random();
        static Queue<int> q = new Queue<int>();
        static int count = 1;
        static void Adder()
        {
            while (true)
            {
                Thread.Sleep(1000 * ((r.Next() % 5) + 1));
                Console.WriteLine("Will try to add");
                q.Add(count++);
            }
        }

        static void Remover()
        {
            while (true)
            {
                Thread.Sleep(1000 * ((r.Next() % 5) + 1));
                Console.WriteLine("Will try to remove");
                int item = q.Remove();
                Console.WriteLine("Removed " + item);
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Test");

            for (int i = 0; i < 5; i++)
            {
                Thread remover = new Thread(Remover);
                remover.Start();
            }

            Thread adder = new Thread(Adder);
            adder.Start();
        }
    }
}

推荐答案

如果我让它运行足够长的时间,我会得到一个异常,因为即使队列为空,其中一个移除器线程也会退出等待状态.有谁知道为什么我会得到例外?

if I leave it running for long enough I get an exception because one of the remover threads comes out of a waiting state even when the queue is empty. Does anyone know why I get the exception?

这个问题很奇怪,因为显然你知道答案:你的第一句话回答了第二句话提出的问题.您会收到异常,因为当队列为空时,移除器线程退出等待状态.

The question is odd, because obviously you know the answer: your first sentence answers the question asked by the second sentence. You get the exception because a remover thread comes out of the wait state when the queue is empty.

要解决问题,您需要使用循环而不是if".正确的代码是:

To solve the problem you'll want to use a loop instead of an "if". The correct code is:

while(q.Count == 0) Monitor.Wait(q);

不是

if(q.Count == 0) Monitor.Wait(q);

更新:

一位评论者指出,您的问题可能是当队列为空时,消费者线程在什么情况下可以获得监视器?"

UPDATE:

A commenter points out that perhaps your question was intended to be "under what circumstances can a consumer thread obtain the monitor when the queue is empty?"

嗯,你比我们更适合回答这个问题,因为你是运行程序并查看输出的人.但就在我的脑海中,这是一种可能发生的方式:

Well, you are in a better position to answer that than we are, since you're the one running the program and looking at the output. But just off the top of my head, here's a way that could happen:

  • 消费者线程 1:等待
  • 消费者线程 2:准备就绪
  • 生产者线程 3:拥有监视器
  • 队列中有一个元素.
  • 线程 3 个脉冲.
  • 线程 1 进入就绪状态.
  • 线程 3 放弃监视器.
  • 线程 2 进入监视器.
  • 线程 2 使用队列中的项目
  • 线程 2 放弃监视器.
  • 线程 1 进入监视器.

现在线程 1 在监视器中,队列为空.

And now thread 1 is in the monitor with an empty queue.

一般而言,在推理此类问题时,您应该将Pulse"视为一只附有便条的鸽子.一旦被释放,它就与发送者没有任何联系,如果它找不到它的家,它就会在旷野中死去,它的信息没有被传递.当您使用 Pulse 时,您所知道的就是 如果 有任何线程在等待,那么一个线程将在未来某个时间进入就绪状态;你对线程操作的相对时间一无所知.

Generally speaking when reasoning about these sorts of problems you should think of "Pulse" as being like a pigeon with a note attached to it. Once released it has no connection to the sender, and if it cannot find its home, it dies in the wilderness with its message undelivered. All you know when you Pulse is that if there is any thread waiting then one thread will move to the ready state at some time in the future; you don't know anything else about the relative timing of operations on threads.

这篇关于在 C# 中实现阻塞队列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:在 C# 中实现阻塞队列