在C#中实现阻塞队列 [英] Implementing a blocking queue in C#

查看:550
本文介绍了在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.

有人知道我为什么会得到例外吗?请注意,我有兴趣知道为什么相对于有效的解决方案它不起作用(我只能用Google来解决).

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进入监视器.
  • Consumer Thread 1: waiting
  • Consumer Thread 2: ready
  • Producer Thread 3: owns the monitor
  • There is one element in the queue.
  • Thread 3 pulses.
  • Thread 1 goes to ready state.
  • Thread 3 abandons the monitor.
  • Thread 2 enters the monitor.
  • Thread 2 consumes the item in the queue
  • Thread 2 abandons the monitor.
  • Thread 1 enters the monitor.

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

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

通常来说,在对这类问题进行推理时,您应该将脉冲"视为具有附加说明的鸽子.一旦释放,它就与发送者没有任何关系,如果找不到发送者的家,它会在荒野中死去,而其消息仍未传递.当您使用Pulse时,您所知道的只是 if ,如果有任何线程在等待,那么将来某个时间一个线程将进入就绪状态;您对线程操作的相对时间一无所知.

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#中实现阻塞队列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆