在一次采访中,我被问到以下问题:
有一个固定大小的任务队列。线程希望对任务进行排队。如果队列已满,则应等待。线程顺序应该保持不变:如果thread1与task1一起出现,而在此之后,thread2与task2一起出现,则task1应该在task2之前输入队列。
其他线程希望将任务排出队列并执行它。如果队列是空的,它们应该等待,它们的顺序也应该保持不变:如果t3位于t4之前,t3应该在t4之前对任务进行排队。
如何实现这一点(在伪代码中)?
发布于 2012-11-08 13:21:14
System.Collections.Concurrent
,其中的类工作得很好--我无法从它们那里获得一些错误。
ConcurrentQueue和BlockingQueue是你开始研究的地方。但我认为你的问题不是关于标准解决方案的--这是面试的糟糕答案,所以:ConcurrentQueue
和ConcurrentStack
都是无锁的;它们都在内部使用Interlocked
方法来操作集合。因此,您必须删除Monitor
类的使用,并为您的线程作为下一个登记项提供检查。这可以通过在私有字段中保持当前加法器的数量、和当前队列长度来实现。您应该将此字段设置为易失性。
您应该使用Interlocked.Exchange获取当前加法器和Interlocked.Read获取当前队列长度。
在此之后,您的线程有唯一的编号-当前长度+当前加法器。使用SpinWait类旋转,而当前长度将不等于您的编号,在该队列项之后,并离开enqueue方法。
我强烈建议你学习这本书中关于多线程和锁的章节--在你的生活中,你会对这类问题做更多的准备。也尝试在这里搜索类似的问题。例如:
发布于 2012-11-08 10:09:14
要同步访问有限数量的资源,通常使用信号量。谷歌让它得到你自己的想法。
困难的部分是保持阻塞线程的顺序。
我在C#:http://dcutilities.codeplex.com中找到了一个包含一个http://dcutilities.codeplex.com的项目
发布于 2012-11-08 10:17:25
如果生产者线程正在等待numEmptySpaces
信号量来访问队列,这种行为可能无论如何都会发生,因为信号量等待队列在FIFO之外的任何实现都是不合理的,但在大多数信号量实现中不能保证这样做。
保证这种行为是非常尴尬的,因为很难定义‘线程顺序’的要求:
如何定义哪个线程最先到达?
如果‘第一个线程’获得了某种类型的锁,从而阻止其他线程继续运行,那么后续的线程就会“立即”进入,因此必须服从操作系统提供的任何锁队列顺序。
我唯一能想到的就是强迫每个生产者线程在试图锁定/排队之前获得一个无锁的时间戳或序列号。这可以通过“正常”原子增量指令来完成。当生产者随后通过获取“numEmptySpaces”单元“进入”并锁定队列时,它可以按序列号顺序将自己排队到队列上。
我不确定是否可以使用标准的BlockingCollection
。您可能能够按序列号“OrderBy”条目,但我不确定这个操作是否锁定了队列-它应该可以,但是..。此外,必须将sequenceNo作为私有备忘录添加到BlockingCollection后代中,并将原子增量结果维护为每个任务的状态--您必须将其添加到Task
成员中。
我很想通过自己的BlockingQueue
类构建一个“正常”队列、耦合信号量和一个互斥体来实现这一点,一旦获得了numEmptySpaces单元和队列互斥体,就按序列号顺序向队列中插入新任务。然后可以将原子增量结果组装到堆栈/auto var中。
这可能是一个面试问题,但我必须受到解雇的威胁,才能在生产代码中实际执行。很难想象在什么情况下它可能是必要的。额外开销和争论的负面影响超过了我所能想到的一切值得怀疑的好处。
对于试图在dequeue/execute端显式维护任何类型的排序,我也有类似的保留意见。试图确保按序号顺序到达脱队列任务中的“检查点”将是一件很麻烦的事情。它需要任务的合作,任务需要私有同步对象成员在到达检查点时发出信号。永远不要尝试:)
https://stackoverflow.com/questions/13284730
复制相似问题