J'ai récemment demandé à un certain nombre de questions concernant l' TVar
, et j'ai toujours des inquiétudes à propos absence de cycle non productif.
Alors j'ai pensé à cette structure:
- Chaque transaction obtient un unique priorité (peut-être alloués dans l'ordre de création).
- Les opérations de tenter d'obtenir de lecture/écriture des verrous sur les données qu'ils ont accès. Naturellement, lectures simultanées sont d'accord, mais un verrou en écriture exclut tous les autres (à la fois en lecture et en écriture).
- Dire Une transaction a priorité plus élevée que les transactions B. Si Une détient le verrou, B attend, mais si B détient le verrou et Un le veut, B est démarré à partir de la serrure, Un obtient, et l'opération B redémarre (comme avec
TVar
). B toutefois conserve sa priorité actuelle pour la nouvelle tentative. - Lorsqu'un verrou est libéré et il y a des transactions en attente, il va à la priorité la plus élevée de la transaction, et le reste à continuer à attendre.
Ce système je crois empêche les blocages, mais aussi de prévenir la famine (contrairement à TVar
). Je me demandais si quelqu'un a mis en place un tel système, car il me semble assez évident, et je ne veux pas réinventer la roue.
Bien sûr, une telle approche pourrait facilement être étendue pour permettre à l'utilisateur de définir les priorités.
La priorité pourrait être la paire (user_supplied_prio, auto_increment)
, avec user_supplied_prio
qui prend le pas, mais l'égalité de priorité des tâches de résolution dans l'ordre FIFO.
Commentaire/Solution:
En fait, quand j'y pense, ce que je décris existe déjà dans Haskell, simplement en utilisant un IORef
enroulé autour de toutes les données, et en utilisant seulement atomicModifyIORef
. L' atomicModifyIORef
permettra de s'assurer que les transactions sont appliqués dans l'ordre. Cependant, on peut penser que cela signifie que la structure de données est séquentielle (c'est à dire effectivement limité à un seul thread) mais c'est en réalité parallèle en raison à la paresse.
Pour expliquer cela, prenons une fonction coûteuse f
. Nous allons l'appliquer à un Data.Map
pour les données avec la clé "foo".
Cela remplace (foo -> x)
avec (foo -> future(f x))
. Ce fil doit continuer à travailler sur ce (f x)
est en fait, mais en attendant, nous pouvons appliquer g de "bar". Depuis l'application g de "bar" n'a pas besoin de la suite de "foo", nous pouvons faire ce travail en même temps.
Pas de blocages, pas de famine, finalement, toutes les transactions seront traitées (à peu près dans l'ordre où ils sont reçus).