Clojure - Programmation simultanée

Dans la programmation Clojure, la plupart des types de données sont immuables, donc en ce qui concerne la programmation simultanée, le code utilisant ces types de données est assez sûr lorsque le code s'exécute sur plusieurs processeurs. Mais souvent, il est nécessaire de partager des données, et lorsqu'il s'agit de données partagées entre plusieurs processeurs, il devient nécessaire de s'assurer que l'état des données est maintenu en termes d'intégrité lorsque vous travaillez avec plusieurs processeurs. Ceci est connu commeconcurrent programming et Clojure fournit un soutien pour une telle programmation.

Le système de mémoire transactionnelle logicielle (STM), exposé via dosync, ref, set, alter, etc. prend en charge le partage de changement d'état entre les threads de manière synchrone et coordonnée. Le système d'agent prend en charge le partage de l'état changeant entre les threads de manière asynchrone et indépendante. Le système d'atomes prend en charge le partage de l'état changeant entre les threads de manière synchrone et indépendante. Alors que le système var dynamique, exposé via def, binding, etc. prend en charge l'isolement de l'état changeant dans les threads.

D'autres langages de programmation suivent également le modèle de la programmation simultanée.

  • Ils ont une référence directe aux données qui peuvent être modifiées.

  • Si un accès partagé est requis, l'objet est verrouillé, la valeur est modifiée et le processus se poursuit pour l'accès suivant à cette valeur.

Dans Clojure, il n'y a pas de verrous, mais des références indirectes à des structures de données persistantes immuables.

Il existe trois types de références dans Clojure.

  • Vars - Les changements sont isolés dans les threads.

  • Refs - Les modifications sont synchronisées et coordonnées entre les threads.

  • Agents - Implique des changements indépendants asynchrones entre les threads.

Les opérations suivantes sont possibles dans Clojure en ce qui concerne la programmation simultanée.

Transactions

La concurrence dans Clojure est basée sur les transactions. Les références ne peuvent être modifiées que dans le cadre d'une transaction. Les règles suivantes sont appliquées dans les transactions.

  • Tous les changements sont atomiques et isolés.
  • Chaque modification d'une référence se produit dans une transaction.
  • Aucune transaction ne voit l'effet d'une autre transaction.
  • Toutes les transactions sont placées à l'intérieur du bloc dosync.

Nous avons déjà vu ce que fait le bloc dosync, revoyons-le.

dosync

Exécute l'expression (dans un do implicite) dans une transaction qui englobe l'expression et tous les appels imbriqués. Démarre une transaction si aucune n'est déjà en cours d'exécution sur ce thread. Toute exception non interceptée annulera la transaction et sortira de dosync.

Voici la syntaxe.

Syntaxe

(dosync expression)

Parameters - 'expression' est l'ensemble des expressions qui viendront dans le bloc dosync.

Return Value - Aucun.

Regardons un exemple dans lequel nous essayons de changer la valeur d'une variable de référence.

Exemple

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (def names (ref []))
   (alter names conj "Mark"))
(Example)

Production

Le programme ci-dessus lorsqu'il est exécuté donne l'erreur suivante.

Caused by: java.lang.IllegalStateException: No transaction running
   at clojure.lang.LockingTransaction.getEx(LockingTransaction.java:208)
   at clojure.lang.Ref.alter(Ref.java:173)
   at clojure.core$alter.doInvoke(core.clj:1866)
   at clojure.lang.RestFn.invoke(RestFn.java:443)
   at clojure.examples.example$Example.invoke(main.clj:5)
   at clojure.examples.example$eval8.invoke(main.clj:7)
   at clojure.lang.Compiler.eval(Compiler.java:5424)
   ... 12 more

À partir de l'erreur, vous pouvez clairement voir que vous ne pouvez pas modifier la valeur d'un type de référence sans lancer au préalable une transaction.

Pour que le code ci-dessus fonctionne, nous devons placer la commande alter dans un bloc dosync comme dans le programme suivant.

Exemple

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (def names (ref []))
   
   (defn change [newname]
      (dosync
         (alter names conj newname)))
   (change "John")
   (change "Mark")
   (println @names))
(Example)

Le programme ci-dessus produit la sortie suivante.

Production

[John Mark]

Voyons un autre exemple de dosync.

Exemple

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (def var1 (ref 10))
   (def var2 (ref 20))
   (println @var1 @var2)
   
   (defn change-value [var1 var2 newvalue]
      (dosync
         (alter var1 - newvalue)
         (alter var2 + newvalue)))
   (change-value var1 var2 20)
   (println @var1 @var2))
(Example)

Dans l'exemple ci-dessus, nous avons deux valeurs qui sont modifiées dans un bloc dosync. Si la transaction réussit, les deux valeurs changeront sinon toute la transaction échouera.

Le programme ci-dessus produit la sortie suivante.

Production

10 20
-10 40