J'ai beaucoup lu sur le talent de Clojure en matière de concurrence, mais aucun des tutoriels que j'ai lus n'explique en réalité la création d'un fil. Faites-vous simplement (.start (Thread. Func)), ou y a-t-il un autre moyen qui m'a échappé?
Clojure fn
s sont Runnable
il est donc courant de les utiliser exactement de la manière que vous avez postée, oui.
user=> (dotimes [i 10] (.start (Thread. (fn [] (println i)))))
0
1
2
4
5
3
6
7
8
9
nil
Une autre option consiste à utiliser agents , auquel cas vous pouvez utiliser send
ou send-off
et utiliser un thread d'un pool.
user=> (def a (agent 0))
#'user/a
user=> (dotimes [_ 10] (send a inc))
nil
;; ...later...
user=> @a
10
Une autre option serait pcalls
et pmap
. Il y a aussi future
. Ils sont tous documentés dans l’API Clojure .
D'habitude, lorsque je veux démarrer un fil dans Clojure, j'utilise simplement future .
En plus d'être simple à utiliser, cela présente l'avantage d'éviter de faire toute interopérabilité désordonnée avec Java pour accéder aux mécanismes de threads Java sous-jacents.
Exemple d'utilisation:
(future (some-long-running-function))
Cela exécutera la fonction de manière asynchrone dans un autre thread.
(def a (future (* 10 10)))
Si vous voulez obtenir le résultat, déréférencez l’avenir, par exemple:
@a
=> 100
Notez que @a bloquera jusqu'à ce que le futur thread ait terminé son travail.
Programmation Clojure ne répond pas à cette question avant la page 167: "Utiliser des agents pour les mises à jour asynchrones".
Avant de commencer les discussions, veuillez noter que Clojure effectuera plusieurs tâches de manière autonome, avec une chance sur deux. J'ai écrit des programmes ignorant allègrement de la concurrence et constaté que, lorsque les conditions sont réunies, ils occupent plus d'un processeur. Je sais que ce n'est pas une définition très rigoureuse: je n'ai pas encore exploré cela en profondeur.
Mais pour les cas où vous avez vraiment besoin d'une activité distincte et explicite, l'une des réponses de Clojure est apparemment l'agent.
(agent initial-state)
va en créer un. Ce n'est pas comme un thread Java, c'est-à-dire un bloc de code en attente d'exécution. Au lieu de cela, il s’agit d’une activité en attente de travail. Vous faites cela via
(send agent update-fn & args)
L'exemple fait
(def counter (agent 0))
counter
est votre nom et votre pseudonyme pour l'agent; l'état de l'agent est le nombre 0.
Une fois cela configuré, vous pouvez envoyer du travail à l'agent:
(send counter inc)
lui dira d'appliquer la fonction donnée à son état.
Vous pouvez extraire ultérieurement l'état de l'agent en le déréférencant:
@counter
vous donnera la valeur actuelle du nombre qui a commencé à 0.
La fonction await
vous permettra de faire quelque chose comme une join
sur l'activité de l'agent, si celle-ci est longue:
(await & agents)
attendra qu'ils aient tous terminé; il y a aussi une autre version qui prend un délai d'attente.
Oui, la manière dont vous démarrez un thread Java dans Clojure ressemble à ce que vous avez là.
Cependant, la question real est la suivante: pourquoi voudriez-vous faire cela? Clojure a beaucoup meilleures constructions de concurrence que les threads.
Si vous regardez l'exemple concurrentiel canonique de Clojure, Simulation de colonie de fourmis de Rich Hickey , vous verrez qu'il utilise exactement 0 threads. La seule référence à Java.lang.Thread
dans l'ensemble de la source est trois appels à Thread.sleep
, dont le seul but est de ralentir la simulation afin que vous puissiez réellement voir ce qui se passe dans l'interface utilisateur.
Toute la logique est faite dans les agents: un agent pour chaque fourmi, un agent pour l'animation et un agent pour l'évaporation de phéromone. Le terrain de jeu est une référence transactionnelle. Pas un fil ni un verrou en vue.
Juste pour ajouter mes deux cents (7 ans plus tard): Les fonctions Clojure implémentent la IFn
interface qui étend Callable
ainsi que Runnable
. Par conséquent, vous pouvez simplement les transmettre à des classes telles que Thread
.
Si votre projet utilise peut-être déjà core.async , je préfère utiliser la macro go
:
(go func)
Ceci exécute func
dans un thread super léger IOC (inversion du contrôle) :
go [...] transformera le corps en machine à états. Lorsque toute opération de blocage est atteinte, la machine à états est "parquée" et le thread réel du contrôle est libéré. [...] Une fois l'opération de blocage terminée, le code reprendra [...]
Si func
doit effectuer des E/S ou une tâche longue, vous devez utiliser thread
, qui fait également partie de core.async (consultez this excellent blog):
(thread func)
Quoi qu'il en soit, si vous souhaitez vous en tenir à la syntaxe d'interopérabilité Java, envisagez d'utiliser la macro ->
(thread/arrow):
(-> (Thread. func) .start)
Utiliser un avenir est généralement l’accès ad hoc le plus simple aux threads. Tout dépend de ce que vous voulez faire :)
La macro (future f)
encapsule le formulaire f dans un objet appelable (via fn *) et le soumet immédiatement à un pool de threads.
si vous avez besoin d'une référence à un objet Java.lang.Thread, par exemple, pour l'utiliser comme crochet de fermeture Java.lang.Runtime, vous pouvez créer un sujet comme celui-ci:
(proxy [Thread] [] (run [] (println "running")))
Cela ne va pas encore démarrer le fil, seulement le créer. Pour créer et exécuter le thread, soumettez-le à un pool de threads ou appelez .start dessus:
(->
(proxy [Thread] [] (run [] (println "running")))
(.start))
La réponse de Brians crée également un fil mais n'a pas besoin de proxy, c'est donc très élégant. D'autre part, en utilisant un proxy, nous pouvons éviter de créer un Callable.