调度工作流程的数据结构?

时间:2009-06-30 15:06:39

标签: algorithm data-structures scheduled-tasks

我想知道哪种数据结构/算法可能有助于处理以下情况;我不确定我是需要单个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!

有什么建议吗?

6 个答案:

答案 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 schedulerUnderstanding 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)

看看这个链接。

Boost state machines vs uml

Boost有状态机。为什么要重新发明?