web-dev-qa-db-fra.com

Comment commence-t-on un fil dans Clojure?

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é?

42
andrewdotnich

Clojure fns 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 .

38
Brian Carper

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.

32
mikera

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.

15
Carl Smotricz

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.

9
Jörg W Mittag

Juste pour ajouter mes deux cents (7 ans plus tard): Les fonctions Clojure implémentent la IFninterface 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)
4
beatngu13

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 :)

2
Timothy Pratley

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.

0
Lars Bohl