C# 按线程顺序同步线程对固定大小队列的访问

C# 按线程顺序同步线程对固定大小队列的访问,c#,multithreading,queue,synchronize,C#,Multithreading,Queue,Synchronize,在一次采访中,我被问到以下问题: 有一个固定大小的任务队列。线程希望将任务排队。如果队列已满,他们应该等待。线程顺序应该保持不变:如果thread1与task1一起出现,然后thread2与task2一起出现,task1应该在task2之前进入队列 其他线程希望将任务出列并执行它。如果队列为空,他们应该等待,并且他们的顺序应该保持不变:如果t3在t4之前,t3应该在t4之前将任务排队 如何实现这一点(在伪代码中)?要同步对有限数量资源的访问,通常使用信号量。用谷歌搜索它来获得你自己的想法 困难的

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

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

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


如何实现这一点(在伪代码中)?

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

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


我在C#中发现这个项目包含一个
FifoSemaphore

如果生产者线程正在等待一个
numemptyspace
信号量来访问队列,这种行为无论如何都可能发生,因为信号量等待队列在FIFO以外的任何地方实现都是不合理的,但在大多数信号量实现中都不能保证这一点

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

如何定义最先到达的线程

如果“第一个线程”获得某种类型的锁,阻止其他线程继续,则后续线程将“立即”进入,因此将服从操作系统提供的任何锁队列顺序

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

我不确定是否可以使用标准的
BlockingCollection
。您可以按序列号“OrderBy”条目,但我不确定此操作是否会锁定队列-应该这样做,但是。。此外,sequenceNo必须作为私有memeber添加到BlockingCollection子体中,并且原子增量结果作为每个任务的状态维护-您必须将其添加到
任务
成员中

我很想用自己的
BlockingQueue
类构建一个“普通”队列,耦合信号量和互斥来实现这一点,一旦获得NumemptySpace单元和队列互斥体,就按序列号顺序将新任务插入队列。然后,可以将原子增量结果组装成堆栈/自动变量

作为一个面试问题,这可能是合理的,但我必须受到解雇的威胁,才能在生产代码中实际实现它。很难想象在什么情况下它可能是必要的。额外开销和争用的负面影响超过了我所能想到的一切可疑的正面影响

对于尝试在出列/执行端显式维护任何排序,我也有类似的保留意见。尝试并确保按序列号顺序到达出列任务中的某些“检查点”将是一件麻烦事。它需要任务的合作,任务需要一个私有的synchro对象成员在到达检查点时发出信号。永远不要尝试:)

  • 简单解 在.NET 4.0中引入了名称空间,其中的类工作得非常正常-我无法从中获得一些错误。
    是你开始研究的地方。 但我认为你的问题不是关于标准解决方案——这是面试中的错误答案,因此:
  • 基于信息的解决方案:
    基本代码(C#):

    内部密封类同步队列{
    私有只读对象m_lock=新对象();
    私有只读队列m_Queue=新队列();
    公共无效排队(T项){
    监控。进入(m_锁);
    //排队购买物品后,叫醒所有服务员
    m_队列。排队(项目);
    监视器脉冲密封(m_锁);
    监视器。退出(m_锁);
    }
    公共T出列(){
    监控。进入(m_锁);
    //队列为空时循环(条件)
    while(m_queue.Count==0)
    监视器。等待(m_锁);
    //将项目从队列中出列并返回以进行处理
    T item=m_queue.Dequeue();
    监视器。退出(m_锁);
    退货项目;
    }
    }
    
    这个类是线程安全的,但是仍然不检查顺序——这里有很多方法来实现它。来自同一本书:

    ConcurrentQueue
    ConcurrentStack
    是无锁的;这些都是内部使用的
    互锁的
    方法来操作集合

    所以,您必须删除
    Monitor
    类用法,并提供检查,确保您的线程是下一个排队项目的线程。 这可以通过在专用字段中保持当前加法器的数量和当前队列长度来实现。您应该创建此字段。
    您应该使用来获取当前加法器,并获取当前队列长度
    之后,您的线程有了唯一的编号-当前长度+当前加法器。当当前长度不等于您的数字时,使用类旋转,在该排队项目之后,并离开排队方法

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

    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;
        }
    }