Section courante

A propos

Section administrative du site

Références et transactions

Alors que les variables (Vars) garantissent une utilisation sécurisée des emplacements de stockage modifiables grâce à l'isolation des processus légers, les références transactionnelles (Refs) assurent une utilisation partagée sécurisée de ces mêmes emplacements via un système de mémoire transactionnelle logicielle (STM). Les Refs sont liées à un emplacement d'entreposage unique pour toute leur durée de vie et n'autorisent la modification de cet emplacement qu'au sein d'une transaction.

Les transactions Clojure sont faciles à comprendre si vous avez déjà utilisé des transactions de bases de données : elles garantissent que toutes les actions sur les Refs sont atomiques, cohérentes et isolées. Atomique signifie que toute modification apportée aux Refs au sein d'une transaction est prise en compte, ou qu'aucune ne l'est. Cohérente signifie que chaque nouvelle valeur peut être vérifiée par une fonction de validation avant que la transaction ne soit validée. Isolée signifie qu'aucune transaction ne subit les effets d'une autre transaction pendant son exécution. Une autre caractéristique commune aux STM est que, en cas de conflit lors de l'exécution d'une transaction, celle-ci est automatiquement relancée.

Il existe de nombreuses façons d'implémenter des STM (avec verrouillage/pessimistes, sans verrouillage/optimistes et hybrides), et cela reste un sujet de recherche. Le STM de Clojure utilise un contrôle de concurrence multiversion avec des files d'attente d'historique adaptatives pour l'isolation des instantanés et propose une opération de commutation distincte.

Concrètement, cela signifie :

  1. Toute lecture de références (Refs) affichera un instantané cohérent du «monde des références» au point de départ de la transaction (son «point de lecture»). La transaction reflétera toutes les modifications qu'elle a apportées. Il s'agit de la valeur en cours de transaction.
  2. Toutes les modifications apportées aux références pendant une transaction (via ref-set, alter ou commute) sembleront se produire à un seul et même point de la chronologie du «monde des références» (son « point d'écriture »).
  3. Aucune modification n'aura été apportée par d'autres transactions aux références qui ont été définies, modifiées ou garanties par cette transaction.
  4. Des modifications peuvent avoir été apportées par d'autres transactions aux références qui ont été commutées par cette transaction. Cela ne devrait pas poser de problème, car la fonction appliquée par commute est commutative.
  5. Les lecteurs et les utilisateurs effectuant une commutation ne bloqueront jamais les rédacteurs, les utilisateurs effectuant une commutation ou les autres lecteurs.
  6. Les rédacteurs ne bloqueront jamais les utilisateurs effectuant une commutation ni les lecteurs.
  7. Les opérations d'entrée/sortie et autres activités ayant des effets de bord doivent être évitées dans les transactions, car celles-ci seront réessayées. La macro `io!` permet d'empêcher l'utilisation d'une fonction impure dans une transaction.
  8. Si une contrainte sur la validité de la valeur d'une référence (`Ref`) en cours de modification dépend de la valeur simultanée d'une référence (`Ref`) non modifiée, cette dernière peut être protégée contre toute modification en appelant `ensure`. Les références ainsi protégées le seront (point n° 3), mais ne modifieront pas le contexte global (point n° 2).
  9. Le STM MVCC de Clojure est conçu pour fonctionner avec les collections persistantes, et il est fortement recommandé d'utiliser les collections Clojure comme valeurs de vos références. Étant donné que tout travail effectué dans une transaction STM est spéculatif, il est impératif que le coût des copies et des modifications soit faible. Les collections persistantes offrent des copies gratuites (utilisez simplement l'original, il ne peut pas être modifié), et les modifications partagent efficacement la structure. Dans tous les cas :
  10. Les valeurs placées dans les références doivent être, ou être considérées comme, immuables ! Sinon, Clojure ne pourra pas vous aider.

Exemple

Dans cet exemple, un vecteur de références à des vecteurs est créé, chacun contenant des nombres uniques (initialement séquentiels). Ensuite, un ensemble de processus légers est lancé, qui sélectionnent et échangent de manière répétée deux positions aléatoires dans deux vecteurs aléatoires, au sein d'une transaction. Aucune mesure particulière n'est prise pour éviter les conflits inévitables, hormis l'utilisation de transactions.

  1. (defn run [nvecs nitems nthreads niters]
  2.   (let [vec-refs (vec (map (comp ref vec)
  3.                            (partition nitems (range (* nvecs nitems)))))
  4.         swap #(let [v1 (rand-int nvecs)
  5.                     v2 (rand-int nvecs)
  6.                     i1 (rand-int nitems)
  7.                     i2 (rand-int nitems)]
  8.                 (dosync
  9.                  (let [temp (nth @(vec-refs v1) i1)]
  10.                    (alter (vec-refs v1) assoc i1 (nth @(vec-refs v2) i2))
  11.                    (alter (vec-refs v2) assoc i2 temp))))
  12.         report #(do
  13.                  (prn (map deref vec-refs))
  14.                  (println "Distinct:"
  15.                           (count (distinct (apply concat (map deref vec-refs))))))]
  16.     (report)
  17.     (dorun (apply pcalls (repeat nthreads #(dotimes [_ niters] (swap)))))
  18.     (report)))

Lors de l'exécution, nous ne constatons aucune perte ni duplication de valeurs lors du mélange :

  1. (run 100 10 10 100000)
  2.  
  3. ([0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] ...
  4.  [990 991 992 993 994 995 996 997 998 999])
  5. Distinct: 1000
  6.  
  7. ([382 318 466 963 619 22 21 273 45 596] [808 639 804 471 394 904 952 75 289 778] ...
  8.  [484 216 622 139 651 592 379 228 242 355])
  9. Distinct: 1000

Fonctions associées

Catégorie Fonctions
Créer une référence ref
Examiner une référence deref (see also the @ reader macro)
Macros de transaction dosync io!
Autorisé uniquement dans le cadre d'une transaction ensure ref-set alter commute
Validateurs de référence set-validator! get-validator


Dernière mise à jour : Lundi, le 2 février 2026