我想知道哪种数据结构/算法可能有助于处理以下情况;我不确定我是需要单个FIFO,优先级队列还是多个FIFO。
我有N个对象必须通过预定义的工作流程。每个对象必须完成步骤1,然后是步骤2,然后是步骤3,然后是步骤4,等等。每个步骤要么快速完成,要么涉及“等待”,这取决于外部完成的事情(如完成文件操作或其他任何操作) )。每个对象都保持自己的状态。如果我必须为这些对象定义一个接口,它将是这样的(下面用伪Java编写,但这个问题与语言无关):
public interface TaskObject
{
public enum State { READY, WAITING, DONE };
// READY = ready to execute next step
// WAITING = awaiting some external condition
// DONE = finished all steps
public int getCurrentStep();
// returns # of current step
public int getEndStep();
// returns # of step which is the DONE case.
public State getState();
// checks state and returns it.
// multiple calls will always be identical,
// except WAITING which can transition to READY or DONE.
public State executeStep();
// if READY, executes next step and returns getState().
// otherwise, returns getState().
}
我需要编写一个单线程调度程序,在“next”对象上调用executeStep()。我的问题是,我不确定我应该使用什么技术来确定“下一个”对象是什么。我希望它是公平的(先到先得,对于不在WAITING状态的对象的第一次服务)。
我的直觉是拥有3个FIFO,READY,WAITING和DONE。在开始时,所有对象都放在READY队列中,并且调度程序重复一个循环,它将第一个对象从READY队列中取出,调用executeStep(),并将其放入适合executeStep()结果的队列中。除非WAITING队列中的项目在状态发生变化时需要进入READY或DONE队列.... argh!
有什么建议吗?
答案 0 :(得分:1)
除了轮询之外,任务对象在从WAITING更改为READY时没有任何通知方式,因此WAITING和READY队列实际上只能是一个。你可以循环调用每个调用executeStep()。如果作为executeStep()的返回值,您收到DONE,那么您将其从该队列中删除并将其粘贴在DONE队列上并忘记它。
如果你想对READY对象给予“更多优先级”并尝试在浪费任何资源轮询WAITING之前运行所有可能的READY对象,你可以像你说的那样维护3个队列,只在你没有任何东西时处理WAITING队列。 READY队列。
我个人会花费一些精力来消除状态的轮询,而是定义一个接口,当状态发生变化时,对象可以用它来通知你的调度程序。
答案 1 :(得分:1)
如果必须是单线程,则可以使用单个FIFO队列来准备就绪和等待对象,并使用线程处理每个对象。如果状态更改为WAITING,则只需将其重新放入队列即可重新处理。
类似(伪代码):
var item = queue.getNextItem();
var state = item.executeStep ();
if (state == WAITING)
queue.AddItem (item);
else if (state == DONE)
// add to collection of done objects
根据executeStep运行所需的时间,您可能需要引入延迟(Sleep not for)以防止紧密的轮询循环。理想情况下,您可以让对象发布状态更改事件,并完全取消轮询。
这是一种在多线程普及之前在硬件和通信软件中常见的倍增方法。
答案 2 :(得分:1)
您可能想要研究操作系统调度程序的设计。例如,查看Linux和* BSD。
Linux调度程序的一些指针:Inside the Linux scheduler和Understanding the Linux Kernel
答案 3 :(得分:0)
注意 - 这不会解决您如何安排的问题,但我会使用一个单独的状态类来定义状态和转换。对象不应该知道他们应该经历什么状态。他们可以被告知他们所处的“步骤”等等。
也有一些模式。
您应该在操作系统上阅读一些内容 - 特别是调度程序。您的示例是该问题的缩小版本,如果您复制相关部分,它应该适合您。
然后您可以添加优先级等。
答案 4 :(得分:0)
满足你的问题要求的最简单的技术是重复迭代所有调用executeStep()的TaskObjects。
这只需要一个构造来保存TaskObjects,它可以是任何可迭代的结构,例如一个数组。
由于TaskObject可以异步从WAITING转换为READY,因此您必须轮询每个您不知道已完成的TaskObject。
从不轮询DONE TaskObjects获得的性能可以忽略不计。它取决于在DONE TaskObject上调用executeStep()的处理负载,它应该很小。
简单的循环轮询确保一旦READY TaskObject执行了一个步骤,它就不会执行另一个步骤,直到所有其他TaskObject都有机会执行。
一个明显的附加要求是检测所有TaskObject何时处于DONE状态,以便您可以停止处理。
为了避免轮询DONE TaskObjects,您需要为每个队列维护一个标志,或者将TaskObjects链接在两个队列中:READY / WAITING和DONE。
如果将TaskObjects存储在数组中,请将其作为记录数组,使用成员DoneFlag和TaskObject。
如果出于某种原因将TaskObjects存储在队列中,并使用可用的enqueue()和dequeue()方法,那么两个队列而不是一个队列的开销可能很小。
-Al。
答案 5 :(得分:0)