web-dev-qa-db-fra.com

Algorithme optimisé pour planifier des tâches avec dépendance?

Il existe des tâches qui lisent dans un fichier, effectuent des traitements et écrivent dans un fichier. Ces tâches doivent être planifiées en fonction de la dépendance. Les tâches peuvent également être exécutées en parallèle. L'algorithme doit donc être optimisé pour exécuter des tâches dépendantes en série et autant que possible en parallèle.

par exemple:

  1. A -> B
  2. A -> C
  3. B -> D
  4. E -> F

Donc, une façon de faire cela serait de lancer les tests 1, 2 et 4 en parallèle. Suivi de 3.

Une autre manière pourrait être exécutée 1, puis 2, 3 et 4 en parallèle.

Un autre pourrait être exécuté 1 et 3 en série, 2 et 4 en parallèle.

Des idées?

20
user2186138

Soit chaque tâche (par exemple, A,B,...) des noeuds dans un graphe acyclique dirigé et définissez les arcs entre les noeuds en fonction de votre 1,2,...

http://en.wikipedia.org/wiki/Topological_sorting

Vous pouvez alors ordre topologique votre graphique (ou utiliser une méthode basée sur la recherche telle que BFS ). Dans votre exemple, C<-A->B->D et E->F so, A & E ont une profondeur de 0 et doivent être exécutés en premier. Ensuite, vous pouvez exécuter F, B et C en parallèle suivi de D.

Regardez aussi PERT .

Mettre à jour:

Comment savoir si B a une priorité supérieure à F?

C'est l'intuition qui se cache derrière le type topologique utilisé pour trouver l'ordre. 

Il trouve d’abord les nœuds racine (pas de bords entrants) (puisqu’il en existe un dans un DAG). Dans votre cas, c'est A & E. Ceci règle la première série d’emplois à compléter. Ensuite, les enfants des nœuds racine (B, C et F) doivent être terminés. Ceci est facilement obtenu en interrogeant votre graphique. Le processus est ensuite répété jusqu'à ce qu'il n'y ait plus de nœuds (travaux) à trouver (terminés).

11
Jacob

Étant donné le mappage entre les éléments et les éléments dont ils dépendent, un tri topologique ordonne les éléments de sorte qu'aucun élément ne précède un élément dont il dépend.

Cette tâche de code Rosetta a une solution en Python qui peut vous indiquer les éléments pouvant être traités en parallèle.

Compte tenu de votre saisie, le code devient:

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) ))

Ce qui génère ensuite cette sortie:

A E
B C F
D

Les éléments d'une ligne de la sortie peuvent être traités dans n'importe quel sous-ordre ou même en parallèle; tant que tous les éléments d'une ligne supérieure sont traités avant les éléments des lignes suivantes afin de préserver les dépendances.

7
Paddy3118

Vos tâches sont un graphe orienté avec (espérons-le) pas de cycles.

Je contient sources et wells (les sources étant des tâches qui ne dépendent pas (ne pas avoir de Edge entrant), les puits étant des tâches qui ne déverrouillent aucune tâche (pas de Edge sortant)).

Une solution simple serait de donner la priorité à vos tâches en fonction de leur utilité (appelons cela U.

Généralement, en commençant par les puits, ils ont une utilité U = 1, car nous voulons qu’ils finissent.

Mettez tous les prédécesseurs des puits dans une liste L du nœud en cours d'évaluation.

Ensuite, en prenant chaque noeud dans L, sa valeur U correspond à la somme des valeurs U des noeuds qui dépendent de lui + 1. Placez tous les parents du noeud actuel dans la liste L

Boucle jusqu'à ce que tous les nœuds aient été traités.

Ensuite, démarrez la tâche qui peut être lancée et obtenez la plus grande valeur U, car c’est celle qui débloquera le plus grand nombre de tâches.

Dans votre exemple 

U(C) = U(D) = U(F) = 1
U(B) = U(E) = 2
U(A) = 4

Cela signifie que vous commencerez d'abord par E si possible, puis par B et C (si possible), puis par D et F

1
njzk2

commencez par générer un ordre topologique de vos tâches. vérifier les cycles à ce stade. Ensuite, vous pouvez exploiter le parallélisme en regardant les antichaînes maximales. En gros, ce sont des ensembles de tâches sans dépendance entre leurs éléments.

pour une perspective théorique, cet article couvre le sujet.

1
collapsar

Sans prendre en compte l'aspect série/parallèle du problème, ce code peut au moins déterminer la solution série globale:

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

Si vous mettez à jour la boucle qui recherche les dépendances entièrement satisfaites pour parcourir la liste entière et exécuter/supprimer des tâches ne dépendant plus de toutes les dépendances en même temps, cela devrait également vous permettre de tirer parti de la réalisation des tâches de la manière suivante: parallèle.

0
flyerfye