Agents et actions désynchronisées
À l'instar des Refs, les Agents offrent un accès partagé à un état modifiable. Alors que les Refs permettent une modification coordonnée et synchrone de plusieurs emplacements, les Agents permettent une modification indépendante et asynchrone d'emplacements individuels. Les Agents sont liés à un seul emplacement d'entreposage durant toute leur durée de vie et n'autorisent la modification de cet emplacement (vers un nouvel état) qu'à la suite d'une action. Les actions sont des fonctions (avec éventuellement des arguments supplémentaires) appliquées de manière asynchrone à l'état d'un Agent et dont la valeur de retour devient le nouvel état de l'Agent. Étant des fonctions, les actions peuvent également être multiméthodes et sont donc potentiellement polymorphes. De plus, l'ensemble des fonctions étant ouvert, l'ensemble des actions prises en charge par un Agent l'est également, ce qui contraste fortement avec les boucles de traitement de messages par correspondance de motifs proposées par certains autres langages.
Les Agents de Clojure sont réactifs, et non autonomes : il n'y a pas de boucle de messages impérative ni de réception bloquante. L'état d'un Agent doit être immuable (de préférence une instance d'une collection persistante Clojure) et toujours immédiatement accessible en lecture par n'importe quel processus léger (via la fonction `deref` ou la macro `@`) sans aucun message. Autrement dit, l'observation ne requiert ni coopération ni coordination.
Les actions d'un Agent prennent la forme `(send agent fn args*)`. `send` (et `send-off`) retourne toujours immédiatement. Plus tard, dans un autre processus léger, le processus suivant se déroule :
- La fonction `fn` est appliquée à l'état de l'Agent, ainsi que les arguments `args`, le cas échéant.
- La valeur de retour de `fn` est transmise à la fonction de validation, si elle a été définie pour l'Agent. Voir `set-validator!` pour plus de détails.
- Si la validation réussit ou si aucune validation n'a été fournie, la valeur de retour de `fn` devient le nouvel état de l'Agent.
- Si des observateurs ont été ajoutés à l'Agent, ils sont appelés. Voir `add-watch` pour plus de détails.
- Si d'autres envois sont effectués (directement ou indirectement) pendant l'exécution de la fonction, ils seront mis en attente jusqu'à ce que l'état de l'agent ait été modifié.
Si une exception est levée par une fonction d'action, aucun dispatch imbriqué n'aura lieu et l'exception sera mise en cache dans l'Agent. Lorsqu'un Agent a des erreurs en cache, toute interaction ultérieure lèvera immédiatement une exception, jusqu'à ce que les erreurs de l'agent soient effacées. Les erreurs de l'agent peuvent être examinées avec `agent-error` et l'agent redémarré avec `restart-agent`.
Les actions de tous les Agents sont entrelacées entre les processus légers d'un bassin de processus légers. À tout moment, au maximum une action par Agent est exécutée. Les actions envoyées à un agent depuis un autre agent ou thread s'exécutent dans l'ordre de leur envoi, potentiellement entrelacées avec des actions envoyées au même agent depuis d'autres sources. `send` doit être utilisé pour les actions limitées par le processeur, tandis que `send-off` est approprié pour les actions susceptibles de bloquer les entrées/sorties.
Les Agents sont intégrés au STM : tout dispatch effectué dans une transaction est conservé jusqu'à sa validation et est annulé en cas de nouvelle tentative ou d'abandon.
Comme pour toutes les fonctionnalités de concurrence de Clojure, aucun verrouillage du code utilisateur n'est impliqué.
Notez que l'utilisation d'agents lance un bassin de processus légers d'arrière-plan non-services empêchant l'arrêt de la JVM. Utilisez shutdown-agents pour terminer ces processus légers et autoriser l'arrêt du système.
Exemple
Cet exemple illustre le test de propagation d'un message en boucle. Une chaîne de m agents est créée, puis une séquence de n actions est envoyée à l'agent en tête de chaîne et relayée à travers celui-ci.
- (defn relay [x i]
- (when (:next x)
- (send (:next x) relay i))
- (when (and (zero? i) (:report-queue x))
- (.put (:report-queue x) i))
- x)
-
- (defn run [m n]
- (let [q (new java.util.concurrent.SynchronousQueue)
- hd (reduce (fn [next _] (agent {:next next}))
- (agent {:report-queue q}) (range (dec m)))]
- (doseq [i (reverse (range n))]
- (send hd relay i))
- (.take q)))
-
- ; 1 million de messages envoyés :
- (time (run 1000 1000))
- ->"Elapsed time: 2959.254 msecs"
Fonctions associées
| Catégorie | Fonctions |
|---|---|
| Créer un agent | agent |
| Examiner un agent | deref (see also the @ reader macro) agent-error error-handler error-mode |
| État de l'agent de changement | send send-off restart-agent |
| Blocage en attente d'un agent | await await-for |
| Validateurs de référence | set-validator! get-validator |
| Observateurs | add-watch remove-watch |
| Gestion des processus légers des agents | shutdown-agents |
| Gestion des erreurs des agents | agent-error restart-agent set-error-handler! error-handler set-error-mode! error-mode |