【问题标题】:Synchronize threads access to fixed size queue by threads order按线程顺序同步线程访问固定大小的队列
【发布时间】:2012-11-08 07:52:09
【问题描述】:

我在一次采访中被问到以下问题:

有一个固定大小的任务队列。线程想要将任务排入队列。如果队列已满,他们应该等待。线程顺序应保持:如果thread1与task1一起出现,并且在thread2与task2一起出现之后,task1应在task2之前进入队列。

其他线程想要出列任务并执行它。如果队列为空,他们应该等待,并且他们的顺序应该保持:如果 t3 在 t4 之前,t3 应该在 t4 之前入队。

如何实现(伪代码)?

【问题讨论】:

    标签: c# multithreading queue synchronize


    【解决方案1】:
    1. 简单的解决方案 在 .NET 4.0 中引入了命名空间 System.Collections.Concurrent,其中的类工作正常 - 我无法从它们那里得到一些错误。
      ConcurrentQueueBlockingQueue 是您开始研究的地方。 但我认为你的问题不是关于标准解决方案 - 这在面试中是不好的答案,所以:
    2. 基于Jeffrey Richter's book信息的解决方案:
      基本代码(C#):

      internal sealed class SynchronizedQueue<T> {
          private readonly Object m_lock = new Object();
          private readonly Queue<T> m_queue = new Queue<T>();
      
          public void Enqueue(T item) {
              Monitor.Enter(m_lock);
              // After enqueuing an item, wake up any/all waiters
              m_queue.Enqueue(item);
              Monitor.PulseAll(m_lock);
              Monitor.Exit(m_lock);
          }
      
          public T Dequeue() {
              Monitor.Enter(m_lock);
              // Loop while the queue is empty (the condition)
              while (m_queue.Count == 0)
                  Monitor.Wait(m_lock);
              // Dequeue an item from the queue and return it for processing
              T item = m_queue.Dequeue();
              Monitor.Exit(m_lock);
              return item;
          }
      }
      

      这个类是线程安全的,但仍然不检查顺序 - 这里有很多实现它的方法。来自同一本书:

      ConcurrentQueueConcurrentStack 是无锁的;这些都在内部使用 Interlocked 操作集合的方法。

      因此,您必须删除 Monitor 类的使用,并检查您的线程是否是下一个将项目入队的线程。 这可以通过在私有字段中维护当前加法器的数量和当前队列长度来完成。您应该将此字段设为volatile
      您应该使用Interlocked.Exchange 来获取您的当前加法器,并使用Interlocked.Read 来获取您的当前队列长度
      之后,您的线程就有了唯一编号 - 当前长度 + 当前加法器。使用SpinWait 类旋转,而当前长度将不等于您的数字,在该入队项之后,并离开入队方法。

    我强烈建议你学习本书中关于多线程和锁的章节——你会在你的生活中为这类问题做好准备。也尝试在这里搜索类似的问题。例如:

    Creating a blocking Queue<T> in .NET?

    【讨论】:

      【解决方案2】:

      如果生产者线程在 numEmptySpaces 信号量上等待访问队列,这种行为可能无论如何都会发生,因为信号量等待队列在 FIFO 以外的任何地方实现是不合理的,但大多数情况下都不能保证信号量实现。

      保证这种行为很尴尬,因为很难定义“线程顺序”的要求:

      如何定义哪个线程先到达?

      如果“第一个线程”获得某种类型的锁,阻止其他线程继续执行,那么后续线程“立即”聚集在其中,因此受操作系统提供的任何锁队列顺序的约束。

      我唯一能想到的就是强制每个生产者线程在尝试锁定/排队任何东西之前获取一个无锁时间戳或序列号。这可以通过“正常”原子增量指令来完成。当生产者随后通过获取“numEmptySpaces”单元“进入”并锁定队列时,它可以按序列号顺序将自己排入队列。

      我不确定是否可以为此使用标准的BlockingCollection。您可能可以按序列号对条目进行“排序”,但我不确定此操作是否会锁定队列 - 它应该这样做,但是.. 此外,必须将 sequenceNo 作为私有成员添加到 BlockingCollection后代和原子增量结果维护为每个任务的状态 - 您必须将其添加到 Task 成员。

      我很想通过自己的 BlockingQueue 类构建一个“正常”队列、耦合信号量和互斥锁来实现这一点,一旦 numEmptySpaces 单元和队列互斥锁具有被收购。然后可以将原子增量结果组装到堆栈/自动变量中。

      这可能是一个面试问题,但我必须受到解雇的威胁才能在生产代码中实际实现它。很难想到它可能是必要的情况。在我能想到的所有事情中,额外开销和争用的负面影响都超过了可疑的好处。

      我对尝试在出队/执行端显式维护任何排序有类似的保留。尝试确保以序列号顺序到达出队任务中的​​某些“检查点”会很麻烦。它需要来自任务的合作,这需要一个私有同步对象成员在它到达其检查点时发出信号。永远不要尝试:)

      【讨论】:

        【解决方案3】:

        要同步对有限数量资源的访问,您通常使用信号量。谷歌让它得到你自己的想法。

        困难的部分是保持阻塞线程的顺序。

        我发现这个项目在 C# 中包含 FifoSemaphorehttp://dcutilities.codeplex.com

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-08-30
          • 1970-01-01
          • 2011-02-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-06-21
          • 1970-01-01
          相关资源
          最近更新 更多