Merge branch 'Jean-master-patch-99793' into 'master'

Concurrent Programming    No race condition ?

See merge request gem-graph-team/gem-graph!14
This commit is contained in:
Jean Sirmai 2021-04-08 10:09:31 +00:00
commit 42d2bacac5
1 changed files with 27 additions and 36 deletions

View File

@ -3,8 +3,8 @@ Concurrent programming for gem-graph
Le thread maître, dans l'ordre: Le thread maître, dans l'ordre:
(1) - cherche, en un temps fini, un espace local dans l'espace global de préemption et, s'il le trouve: (1) - cherche, en un temps fini, un espace local dans l'espace global de préemption et, s'il le trouve:
+ tente, en un temps fini, de le préempter et, si la préemption a été possible, + tente, en un temps fini, de le préempter et, si la préemption a été possible,
+ crée un thread de calcul en lui communiquant l'adresse de cet espace local + tente, en un temps fini, de réserver une place dans la liste des threads
+ et l'inscrit dans la liste des threads) + en cas de succès, crée un thread de calcul en lui communiquant les adresses
(2) - cherche, en un temps fini, dans la liste des threads s'il y a qui ont fini leur calcul et, s'il y en a: (2) - cherche, en un temps fini, dans la liste des threads s'il y a qui ont fini leur calcul et, s'il y en a:
+ lève la préemption de leur espace local et + lève la préemption de leur espace local et
@ -13,17 +13,18 @@ Le thread maître, dans l'ordre:
(3) - affiche les résultats. (3) - affiche les résultats.
Chaque thread de calcul, dans l'ordre: Chaque thread de calcul, dans l'ordre:
(1) - lit son espace local et les règles écrites dans l'arbre des transitions et les compare. (1) - lit son espace local graphique et les règles écrites dans l'arbre des transitions et les compare.
(pour faire ce calcul, il n'écrit que dans des variables locales) (pour faire ce calcul, il n'écrit que dans des variables locales)
si échec, il se termine, sinon: si échec, il se termine, sinon:
(2) - écrit le résultat de son calcul dans l'espace global si une règle peut être appliquée. (2) - si une règle peut être appliquée, écrit le résultat de son calcul dans l'espace global.
(3) - écrit qu'il est terminé dans une liste où le thread maître peut lire et écrire. (3) - dans tous les cas, écrit qu'il est terminé dans une liste où le thread maître peut lire et écrire.
si succès, cette écriture est sa dernière instruction. (après, il est terminé) cette écriture est sa dernière instruction. (après, il est terminé)
---
> engine (top-level) boucle infinie: > engine (top-level ?) > thread maître en boucle infinie:
opération type ressource thread maître thread de calcul opération type ressource thread maître thread de calcul
--------- ---- ---------- ------------- ---------------- --------- ---- ---------- ------------- ----------------
@ -35,11 +36,11 @@ préemption espace local section critique espace global de préemption
si confirmation: boucle finie espace global de préemption écriture si confirmation: boucle finie espace global de préemption écriture
fin section c. espace global de préemption ouverture verrou fin section c. espace global de préemption ouverture verrou
si succès préemption: si succès préemption:
création d'un thread section critique liste des threads fermeture verrou recherche d'une place thread section critique liste des threads fermeture verrou
boucle finie liste des threads lecture boucle finie liste des threads lecture
si succès, assignment liste des threads écriture si succès, assignment liste des threads écriture
fin section c. liste des threads ouverture verrou fin section c. liste des threads ouverture verrou
assignment mémoire / CPU système écriture si succès, création d'un thread assignment mémoire / CPU système écriture
recherche règle de transition parcours d'arbre arbre des transitions lecture recherche règle de transition parcours d'arbre arbre des transitions lecture
espace global graphique lecture espace global graphique lecture
@ -51,9 +52,9 @@ terminaison d'un thread section critique liste des threads
fin section c. liste des threads ouverture verrou fin section c. liste des threads ouverture verrou
fin du thread fin du thread
levée préemption espace local section critique liste des threads fermeture verrou recherche place espace local section critique liste des threads fermeture verrou
boucle finie liste des threads lecture boucle finie liste des threads lecture
si succès, assignment liste des threads écriture si succès, levée préemption assignment liste des threads écriture
fin section c. liste des threads ouverture verrou fin section c. liste des threads ouverture verrou
thread d'affichage boucle finie recherche résultats lecture thread d'affichage boucle finie recherche résultats lecture
@ -65,35 +66,22 @@ Trois ressources doivent être accessibles aux threads de calcul de l'automate:
- l'état local (c-a-d: une partie de l'espace global préemptée) pour: - l'état local (c-a-d: une partie de l'espace global préemptée) pour:
(1) lire les contraintes nécéssaires pour le choix de la règle de transition (1) lire les contraintes nécéssaires pour le choix de la règle de transition
(2) puis écrire le résultat de son calcul en cas de succès (2) puis écrire le résultat de son calcul en cas de succès
- la liste des threads en écriture pour indiquer au thread maître qu'ils vont se terminer
Le thread n'est crée que si la préemption a été possible Un thread de calcul n'est crée que si la préemption a été possible.
Son calcul aboutit au choix de l'une des règles ou échoue Son calcul aboutit au choix de l'une des règles ou échoue.
comme les threads ont seulement besoin de lire l'arbre des règles pendant leur calcul. Comme les threads ont seulement besoin de lire l'arbre des règles pendant leur calcul,
Ces lectures ne causent pas de risque de race condition. leurs lectures ne causent pas de risque de race condition.
L'espace global de préemption et la liste des threads sont les seuls objets où plusieurs threads doivent écrire: La liste des threads est le seul objet où plusieurs threads doivent écrire:
- le thread maître doit écrire dans les deux au début du calcul (quand il préempte et crée un thread) et - le thread maître au début du calcul (quand il a préempté et va créer un thread) et
- chaque thread doit écrire dans les deux quand il finit son calcul : - chaque thread quand il va se finir (pour que le thread maître sache qu'il peut lever la préemption)
+ son résultat dans l'espace global (si son calcul a abouti) et
+ son état dans la liste des threads (sinon, le thread maître ne saura pas qu'il peut lever la préemption)
Des deadlocks sont possibles si: Un point délicat est qu'il ne faut pas que l'espace global de préemption soit modifié entre:
- le thread maître a acquis l'espace global de préemption et attend pour la liste des threads
- un thread de calcul a acquis la liste des threads et attend pour l'espace global de préemption
Mais si les acquisitions se font dans le même ordre pour tous les threads (maître et calcul):
en premier l'espace global et en second la liste des threads, alors il ne peut y avoir de deadlock
car aucun thread ne pourra à verrouiller la liste des threads tant qu'il n'a pas eu accès à l'espace global.
Une première solution est donc de ne réaliser que simultanément l'ensemble des opérations
sur l'espace global et la liste des threads (en début comme en fin de calcul)
et de ne les réaliser que lorsque les deux ressources ont été acquises
par le thread maître en début de calcul ou par le thread de calcul lorsqu'il a fini.
Cette solution est coûteuse en temps d'attente.
Le point délicat est qu'il ne faut pas que l'espace global de préemption soit modifié entre:
- le moment où le thread maître vient de trouver un espace local convenable et - le moment où le thread maître vient de trouver un espace local convenable et
- le moment où il a fini de préempter cet espace local. - le moment où il a fini de préempter cet espace local.
Il faut donc qu'il soit vérifié après mise en place du verrou sur l'espace global
et avant préemption (et levée du verrou)
Il ne semble pas y avoir de risque entre: Il ne semble pas y avoir de risque entre:
- le moment où un thread de calcul a fini de modifier son espace local, et - le moment où un thread de calcul a fini de modifier son espace local, et
@ -102,5 +90,8 @@ Il ne semble pas y avoir de risque entre:
La préemption et sa levée doivent être des opérations atomiques La préemption et sa levée doivent être des opérations atomiques
David Beazley & Brian K.Jones Python Cookbook 2013 p503: David Beazley & Brian K.Jones Python Cookbook 2013 p503:
"The solution in which locks are always acquired in strict order [...] can be mathematically proven to avoid deadlocks [because] you can't get cyclic locking dependencies, which are a necessary condition fordeadlock to occur." "The solution in which locks are always acquired in strict order [...]
can be mathematically proven to avoid deadlocks
[because] you can't get cyclic locking dependencies,
which are a necessary condition fordeadlock to occur."