ファイルから読み取り、処理を行い、ファイルに書き込むタスクがあります。これらのタスクは、依存関係に基づいてスケジュールされます。また、タスクは並列で実行できるため、依存するタスクを逐次および可能な限り並列で実行するようにアルゴリズムを最適化する必要があります。
例えば:
したがって、これを実行する1つの方法は、1、2、および4を並行して実行することです。続いて3。
別の方法では、1を実行してから、2、3、4を並行して実行できます。
もう1つは、1と3をシリアルで、2と4をパラレルで実行できます。
何か案は?
各タスク(例:A,B,...
)を 有向非循環グラフ のノードとし、1,2,...
に基づいてノード間のアークを定義します。
次に、 トポロジカル順序 グラフを作成できます(または [〜#〜] bfs [〜#〜] などの検索ベースのメソッドを使用します)。あなたの例では、C<-A->B->D
とE->F
がそうなので、A
&E
の深さは0であり、最初に実行する必要があります。次に、F
、B
とC
を並行して実行し、続いてD
を実行します。
また、 [〜#〜] pert [〜#〜] もご覧ください。
B
がF
よりも優先度が高いかどうかをどのようにして知ることができますか?
これは、順序を見つけるために使用されるトポロジカルソートの直感です。
最初にルート(着信エッジなし)ノードが検出されます(DAGに存在する必要があるため)。あなたの場合、それはA
&E
です。これにより、完了する必要のある最初のジョブが確定します。次に、ルートノードの子(B
、C
およびF
)を終了する必要があります。これは、グラフをクエリすることで簡単に取得できます。次に、検出(終了)するノード(ジョブ)がなくなるまで、このプロセスを繰り返します。
アイテムとそれらが依存するアイテムとの間のマッピングが与えられた場合、トポロジカルソートは、依存するアイテムの前にアイテムがないようにアイテムを並べ替えます。
このRosettaコードタスク には Pythonでのソリューション があり、これにより、並列処理できるアイテムを確認できます。
入力を与えると、コードは次のようになります。
try:
from functools import reduce
except:
pass
data = { # From: http://stackoverflow.com/questions/18314250/optimized-algorithm-to-schedule-tasks-with-dependency
# This <- This (Reverse of how shown in question)
'B': set(['A']),
'C': set(['A']),
'D': set(['B']),
'F': set(['E']),
}
def toposort2(data):
for k, v in data.items():
v.discard(k) # Ignore self dependencies
extra_items_in_deps = reduce(set.union, data.values()) - set(data.keys())
data.update({item:set() for item in extra_items_in_deps})
while True:
ordered = set(item for item,dep in data.items() if not dep)
if not ordered:
break
yield ' '.join(sorted(ordered))
data = {item: (dep - ordered) for item,dep in data.items()
if item not in ordered}
assert not data, "A cyclic dependency exists amongst %r" % data
print ('\n'.join( toposort2(data) ))
次に、この出力が生成されます。
A E
B C F
D
出力の1行の項目は、任意のサブオーダーで、または実際には並行して処理できます。依存関係を維持するために、上位の行のすべてのアイテムが後続の行のアイテムの前に処理される限り。
タスクは、(うまくいけば)サイクルのない方向性のあるグラフです。
私はsources
とwells
を含みます(ソースは依存しないタスク(インバウンドEdgeがない)、ウェルはタスクをロック解除しないタスク(アウトバウンドEdgeがない)です)。
簡単な解決策は、その有用性に基づいてタスクを優先することです(U
と呼びましょう)。
通常、ウェルから始めて、それらには有用性がありますU = 1
、私たちが彼らに仕上げてほしいので。
現在評価されているノードのリストL
にすべてのウェルの先行ノードを入れます。
次に、L
の各ノードを取得すると、そのU
値は、ノードに依存するノードのU
値の合計+ 1になります。現在のノードのすべての親を配置しますL
リストにあります。
すべてのノードが処理されるまでループします。
次に、開始できるタスクで最大のU
値を持つタスクを開始します。これは、最大数のタスクのロックを解除するタスクであるためです。
あなたの例では、
U(C) = U(D) = U(F) = 1
U(B) = U(E) = 2
U(A) = 4
つまり、可能であれば最初にAをEで始め、次にBとC(可能であれば)、次にDとFから始めます。
まず、タスクのトポロジー的な順序を生成します。この段階でサイクルを確認します。その後、最大アンチチェーンを調べることで並列処理を利用できます。大まかに言えば、これらは要素間の依存関係のないタスクセットです。
理論的な観点から、 この論文 はこのトピックをカバーしています。
問題のシリアル/パラレルの側面を考慮せずに、このコードは少なくともシリアルソリューション全体を決定できます。
def order_tasks(num_tasks, task_pair_list):
task_deps= []
#initialize the list
for i in range(0, num_tasks):
task_deps[i] = {}
#store the dependencies
for pair in task_pair_list:
task = pair.task
dep = pair.dependency
task_deps[task].update({dep:1})
#loop through list to determine order
while(len(task_pair_list) > 0):
delete_task = None
#find a task with no dependencies
for task in task_deps:
if len(task_deps[task]) == 0:
delete_task = task
print task
task_deps.pop(task)
break
if delete_task == None:
return -1
#check each task's hash of dependencies for delete_task
for task in task_deps:
if delete_key in task_deps[task]:
del task_deps[task][delete_key]
return 0
完全に満たされている依存関係をチェックするループを更新してリスト全体をループし、依存関係がなくなったタスクを同時に実行/削除すると、タスクの完了を活用できるようになります。平行。