【问题标题】:Queue<T>.Dequeue returns nullQueue<T>.Dequeue 返回 null
【发布时间】:2011-01-16 04:08:49
【问题描述】:

我有一个场景

  • 多个线程正在将数据推送到队列中

  • 只有一个线程正在使用以下代码处理数据

代码-

  while ( Continue )
  {
        while ( queue.Count > 0 )
        {
             MyObj o = queue.Dequeue();
             someProcess(o);
        }
        myAutoResetEvent.WaitOne();
  }

但有时,queue.Dequeue() 在上述场景中返回 null 什么给了?

【问题讨论】:

    标签: c# multithreading synchronization


    【解决方案1】:

    你需要阅读this blog post

    此外,这里有一个用于线程间通信的“通道”的极简骨架:

    public class Channel<T>
    {
        private readonly Queue<T> _queue = new Queue<T>();
    
        public void Enqueue(T item)
        {
            lock (_queue)
            {
                _queue.Enqueue(item);
                if (_queue.Count == 1)
                    Monitor.PulseAll(_queue);
            }
        }
    
        public T Dequeue()
        {
            lock (_queue)
            {
                while (_queue.Count == 0)
                    Monitor.Wait(_queue);
    
                return _queue.Dequeue();
            }
        }
    }
    

    【讨论】:

    • 看起来很有趣,但我更愿意使用最少的锁定,也许是时候详细了解一下 powerthreading 库了
    • 如果你想从多个线程使用标准容器,上面的代码是最少的!但无论如何,powerthreading 还是值得一看的。
    【解决方案2】:

    您需要同步对队列的访问。将lock 语句放在访问队列(读取和写入)的所有代码段周围。如果您从多个线程同时访问队列,则内部结构可能会损坏,并且几乎任何事情都可能发生。

    【讨论】:

    • 如果“访问”意味着阅读,那么只有 1 个读者休息(2)是作家
    • @Kumar:让两个编写器(在不同的线程上)添加到本质上不是线程安全的集合中会导致问题。古法是对的;您需要同步访问。请参阅我的回答以获得一种随意的解释。
    • 嗯...您可以使用java.util.concurrent.SynchronousQueue 代替锁定语句吗?我还没有处理过这门课。
    • 如果您使用的是 .NET 4,则可以使用 ConcurrentQueue 类型避免锁定:codethinked.com/post/2010/02/04/…
    • 请注意,在这种情况下,排他锁是有意义的。如果场景有很多读者,一个作家,而不是相反,那么 ReaderWriterLockSlim 将是更好的解决方案。
    【解决方案3】:

    你说:

    多个线程在一个队列中推送数据

    Queue&lt;T&gt;.Enqueue 方法不是线程安全的。这意味着工作在 Enqueue 方法中完成,如果多个线程正在调用该方法,则该方法需要同步。一个简单的例子是更新Count 属性。可以肯定的是,在 Enqueue 方法的某处有一行看起来像这样:

    ++count;
    

    但众所周知,这不是原子操作。真的更像这样(就实际发生的情况而言):

    int newCount = count + 1;
    count = newCount;
    

    假设count 当前为 5,线程 1 超过了int newCount = count + 1...然后线程 1 认为,“好的,现在计数为 5,所以我将其设为 6。”但是下一个执行的操作是线程 2 到达 int newCount = count + 1 并认为与线程 1 相同的事情(“计数现在是 6”)。所以两个项目刚刚添加到队列中,但计数只从 5 变为 6。

    这只是一个非常基本的例子,说明当访问不同步时,像Queue&lt;T&gt;.Enqueue 这样的非线程安全方法会如何搞砸。它没有具体解释您的问题中发生了什么;我的意图只是指出 您正在做的事情不是线程安全的,并且会导致意外行为

    【讨论】:

      【解决方案4】:

      Guffa 是对的,多线程读写队列会出问题,因为 Queue 不是线程安全的。

      如果您使用的是 .NET 4,请使用线程安全的 ConcurrentQueue<T> 类。如果您不在 .NET 3 或更早版本上,您可以像 Guffa 指出的那样进行自己的锁定,或者使用 3rd 方库。

      【讨论】:

      【解决方案5】:

      确保没有将null 值推入队列。 nulls 允许作为排队值。另外,根据this document,只有Queue&lt;T&gt; 的静态成员是线程安全的,所以要注意跨线程读写。

      【讨论】:

      • 您误解了该文件。就是说只有Queue 的静态成员 才能保证是线程安全的。将队列分配给静态变量并不会神奇地使其成为线程安全的。
      • 没有空值被压入
      • 跟进 SLaks 的评论:Queue&lt;T&gt; 没有任何静态成员。那一点文档是无关紧要的样板文件(至少对于该类的当前版本 - 2.0 和 3.0 框架中的版本每个都有一个静态方法,ReferenceEquals)。
      • 谢谢@SLaks 和@Jeff。这更有意义。
      【解决方案6】:

      你为什么不这样解决问题?

      while (IsRunning)
      {
          do
          {
              MyObj myObj = queue.Dequeue();
              if (myObj != null)
              {
                  DoSomethingWith(myObj);
              }
          } while (myObj != null);
      
          myAutoResetEvent.WaitOne();
      }
      

      更新

      好的,在阅读了 Earwickers 的评论和所有其他答案之后,你没问题,我只是假的。所以请不要在多线程上下文中使用上面的代码

      【讨论】:

      • 因为QueueDequeue 方法不是线程安全的。如果读者在尝试插入内容的同时尝试执行此操作,那么结果将......不可预测。
      【解决方案7】:

      如果你碰巧使用的是非泛型队列(我不建议使用它),你可以使用 Queue.Synchronized 方法来获得一个线程安全的包装器:

      Queue queue = Queue.Synchronized(new Queue());
      

      否则你应该像其他人建议的那样小心锁定自己。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-25
        • 1970-01-01
        • 2011-09-23
        • 1970-01-01
        相关资源
        最近更新 更多