我想实现动态多时间轴队列。这里的上下文通常是调度。
这仍然很简单:这是任务的时间表,每个事件都有其开始和结束时间。任务被分组为作业。这组任务需要保持其顺序,但可以在整体上及时移动。例如,它可以表示为:
--t1-- ---t2.1-----------t2.2-------
' ' ' ' '
20 30 40 70 120
我会将此作为heap queue实现,并带有一些额外的约束。 Python sched
模块在这个方向上有一些基本的方法。
一个队列代表资源,任务需要资源。图形示例:
R1 --t1.1----- --t2.2----- -----t1.3--
/ \ /
R2 --t2.1-- ------t1.2-----
当任务可以使用多个资源之一时,它会变得很有趣。另一个约束是可以在同一资源上运行的连续任务必须使用相同的资源。
示例:如果(从上方)任务t1.3
可以在R1
或R2
上运行,则队列应如下所示:
R1 --t1.1----- --t2.2-----
/ \
R2 --t2.1-- ------t1.2----------t1.3--
start
开始的第一个空闲时段,其中duration
有空闲时间(请参阅最后的详细说明)。 FirstFreeSlot
,尽可能在多个资源上排队作业。
关键是:我如何表示此信息以提供有效的功能?实施取决于我;-)
更新:需要考虑的另一点:典型的区间结构侧重于“X点是什么?”但在这种情况下,enqueue
因此问题是“持续时间D的第一个空槽在哪里?”更重要的是。因此,段/间隔树或此方向的其他内容可能不是正确的选择。
进一步详细说明空闲时隙:由于我们有多个资源和分组任务的约束,因此某些资源上可以有空闲时隙。简单示例:t1.1
在R1上运行40,然后t1.2
在R2上运行。因此,R2上有一个[0, 40]
的空间隔,可以由下一个作业填充。
更新2 :有一个interesting proposal in another SO question。如果有人可以将它移植到我的问题并显示它适用于这种情况(特别是详细说明了多个资源),这可能是一个有效的答案。
答案 0 :(得分:2)
class Task:
name=''
duration=0
resources=list()
class Job:
name=''
tasks=list()
class Assignment:
task=None
resource=None
time=None
class MultipleTimeline:
assignments=list()
def enqueue(self,job):
pass
def put(self,job):
pass
def delete(self,job):
pass
def recalculate(self):
pass
这是您正在寻找的方向的第一步,即用Python编写的数据模型吗?
更新
因此我的效率更高:
它基本上将所有任务放在按结束时间排序的链表中。
class Task:
name=''
duration=0 # the amount of work to be done
resources=0 # bitmap that tells what resources this task uses
# the following variables are only used when the task is scheduled
next=None # the next scheduled task by endtime
resource=None # the resource this task is scheduled
gap=None # the amount of time before the next scheduled task starts on this resource
class Job:
id=0
tasks=list() # the Task instances of this job in order
class Resource:
bitflag=0 # a bit flag which operates bitwisely with Task.resources
firsttask=None # the first Task instance that is scheduled on this resource
gap=None # the amount of time before the first Task starts
class MultipleTimeline:
resources=list()
def FirstFreeSlot():
pass
def enqueue(self,job):
pass
def put(self,job):
pass
def delete(self,job):
pass
def recalculate(self):
pass
由于enqueue
和put
的更新,我决定不使用树。
由于put
及时移动任务,我决定不使用绝对时间。
FirstFreeSlot
不仅返回具有空闲插槽的任务,还返回其他正在运行的任务及其结束时间。
enqueue
的工作原理如下:
我们在FirstFreeSlot
寻找一个免费插槽,并在此安排任务。
如果有足够的空间用于下一个任务,我们也可以安排它。
如果不是:如果有可用空间,请查看正在运行的其他任务。
如果不是:使用此时间参数和正在运行的任务运行FirstFreeSlot
。
改进:
如果put
不经常使用,而enqueue
从零时开始,我们可以通过在每个包含其他正在运行的任务的任务中包含dict()来跟踪重叠任务。然后,每个资源保留一个list()也很容易,其中包含计划任务,其中该资源的绝对时间由endtime排序。只包含那些比以前具有更大时间间隔的任务。现在我们可以更轻松地找到一个免费插槽。
问题:
是否需要执行put
计划的任务?
如果是:如果要安排的另一个任务重叠怎么办?
所有资源都能快速执行任务吗?
答案 1 :(得分:2)
让我们首先将自己局限于最简单的案例:找到一个合适的数据结构,以便快速实现 FirstFreeSlot()。
空闲时隙位于二维空间中:一维是开始时间s,另一维是长度d。 FirstFreeSlot(D)有效地回答了以下问题:
min s:d> = D
如果我们将s和d视为笛卡尔空间(d = x,s = y),这意味着找到由垂直线界定的子平面中的最低点。一个quad-tree,可能在每个节点中都有一些辅助信息(即所有叶子上的最小值),将有助于有效地回答这个问题。
对于 Enqueue(),面对资源限制,请考虑为每个资源维护一个单独的四叉树。四叉树也可以回答诸如
之类的查询min s:s> = S& d> = D
(限制起始数据所需)以类似的方式:现在切掉一个矩形(在左上方打开),我们在那个矩形中查找min。
Put()和 Delete()是四叉树的简单更新操作。
重新计算()可以通过删除() + 放置()来实现。为了节省不必要操作的时间,定义足够的(或理想情况下,足够的+必要的)条件来触发重新计算。 Observer模式可能对此有所帮助,但请记住将重新安排的任务放入FIFO队列或按开始时间排序的优先级队列。 (您希望在接管下一个任务之前完成当前任务的重新安排。)
更一般地说,我确信你知道大多数类型的调度问题,特别是那些有资源限制的问题,至少是NP完全的。因此,在一般情况下,不要指望算法具有不错的运行时间。
答案 2 :(得分:1)
花了一些时间思考这个。我认为细分树可能更适合对此时间轴队列进行建模。工作概念就像LIST数据结构。
我认为Task可以像这样建模(PSEUDO CODE)。 start_time可以确保作业中的任务顺序。
class Task:
name=''
_seg_starttime=-1;
#this is the earliest time the Task can start in the segment tree,
#a lot cases this can be set to -1, which indicates its start after its predecessor,
#this is determined by its predecessor in the segment tree.
#if this is not equal -1, then means this task is specified to start at that time
#whenever the predecessor changed this info need to be taken care of
_job_starttime=0;
#this is the earliest time the Task can start in the job sequence, constrained by job definition
_duration=0;
#this is the time the Task cost to run
def get_segstarttime():
if _seg_starttime == -1 :
return PREDESSOR_NODE.get_segstarttime() + _duration
return __seg_startime + _duration
def get_jobstarttime():
return PREVIOUS_JOB.get_endtime()
def get_starttime():
return max( get_segstarttime(), get_jobstarttime() )
示例(不考虑作业约束)
t1.1( _segst = 10, du = 10 )
\
t2.2( _segst = -1, du = 10 ) meaning the st=10+10=20
\
t1.3 (_segst = -1, du = 10 ) meaning the st = 20+10 = 30
如果我们做Put:
t1.1( _segst = 10, du = 10 )
\
t2.2( _segst = -1, du = 10 ) meaning the st=20+10=30
/ \
t2.3(_segst = 20, du = 10) t1.3 (_segst = -1, du = 10 ) meaning the st = 30+10 = 30
如果我们执行删除t1.1到原始方案
t2.2( _segst = 20, du = 10 )
\
t1.3 (_segst = -1, du = 10 ) meaning the st = 20+10 = 30
可以使用此间隔树的1个实例来表示每个资源 蛋
从段树(时间轴)的角度来看:
t1.1 t3.1
\ / \
t2.2 t2.1 t1.2
从工作角度来看:
t1.1 <- t1.2
t2.1 <- t2.2
t3.1
t2.1和t2.2使用链表连接,如下所述:t2.2从段树中获取_sg_start_time,从链表中获取_job_start_time,比较两次然后实际的最早时间可以派生出来。
答案 3 :(得分:0)
我最后只使用了一个简单的队列项列表和一个用于存储空槽的内存中SQLite数据库,因为SQL的多维查询和更新非常有效。我只需要在表格中存储 start , duration 和 index 字段。