Danger! Ce qui ne fonctionnera pas
Même si Clojure vous offre d'excellentes structures de données et primitives, cela ne vous sauvera pas de la création de conditions de concurrence. Découvrez ces exemples qui ne fonctionneront pas :
;; NE FONCTIONNERA PAS
(def queue (atom '(:foo :bar :baz :qux)))
;; le code ci-dessous est appelé à partir de threads
;; prenez la première valeur de la "queue", en déréférençant
(let [peeked-val (first @queue)]
(do-something-with peeked-val)
;; mettez à jour l'atome pour supprimer la première valeur
;; DANGER: Vous avez déréférencé la valeur ci-dessus mais vous la swappez en une étape distincte
;; (ici). D'autres threads peuvent avoir lu la même valeur ou l'ont potentiellement mutée
;; entre-temps!
(swap! queue rest))
Et les ref
s?
;; NE FONCTIONNERA PAS
(def queue (ref '(:foo :bar :baz :qux)))
;; le code ci-dessous est appelé à partir de threads
;; prenez la première valeur de la "queue", en déréférençant, cette fois dans une transaction!
(dosync
(let [peeked-val (first @queue)]
(do-something-with peeked-val)
;; mettez à jour le ref pour supprimer la première valeur dans la transaction
;; DANGER: Les Refs vous offrent une cohérence transactionnelle (c'est-à-dire la cohérence
;; entre l'accès en lecture/écriture à d'autres refs) mais cela ne s'applique pas vraiment
;; ici car nous n'avons qu'un seul ref. D'autres threads peuvent avoir lu la même valeur
;; ou l'ont potentiellement mutée entre-temps!
(alter queue rest)))
Résolvez-le en Clojure
Vous pouvez accomplir cela avec les structures de données et les atomes de Clojure. La clé est d'utiliser swap-vals!
pour ne toucher l'atome qu'une seule fois - sinon vous rencontrerez des conditions de concurrence car vous avez deux opérations comme dans l'exemple ci-dessus : Déréférencer l'atome (obtenir sa valeur) et le swap (changer sa valeur).
(def queue (atom '(:foo :bar :baz :qux)))
;; utilisez `swap-vals!` sur les atomes pour obtenir à la fois les anciennes et nouvelles valeurs de l'atome.
;; parfait si vous voulez peek (obtenir la première valeur) tout en faisant un pop (supprimer
;; la première valeur de l'atome, modifiant ainsi son état)
;; utilisez le code ci-dessous dans des threads (ou quelque part dans core.async)
;; il est quelque peu non-idiomatique d'utiliser `rest` et `first`, consultez le texte ci-dessous pour
;; d'autres options
(let [[old new] (swap-vals! queue rest)]
(println "voici la valeur extraite :" (first old)))
Vous pouvez également utiliser une PersistentQueue à la place, construisez-la avec (clojure.lang.PersistentQueue/EMPTY)
- ensuite vous pouvez appeler peek
et pop
dessus au lieu de first
et rest
. N'oubliez pas de la mettre dans un atome comme la liste ci-dessus :)
Utilisez les Structures de Données Java
Vous pourriez aussi utiliser quelque chose comme un java.util.concurrent.LinkedBlockingDeque. Regardez ce commit qui introduit des fonctionnalités de compilation parallèle au compilateur ClojureScript pour un exemple d'utilisation d'un LinkedBlockingDeque
.