Подтвердить что ты не робот

Как запустить поток в Clojure?

Я много читал о том, насколько велика Clojure, когда дело доходит до concurrency, но ни один из обучающих программ, которые я прочитал, на самом деле не объясняет, как создать поток. Вы просто делаете (.start(Thread. Func)), или есть другой способ, который я пропустил?

4b9b3361

Ответ 1

Clojure fn являются Runnable, поэтому он распространен, чтобы использовать их точно так же, как вы опубликовали, да.

user=> (dotimes [i 10] (.start (Thread. (fn [] (println i)))))
0                                                             
1                                                             
2                                                             
4                                                             
5                                                             
3                                                             
6                                                             
7                                                             
8                                                             
9                                                             
nil

Другой вариант - использовать agents, в этом случае вы бы send или send-off, и он будет использовать Thread из бассейна.

user=> (def a (agent 0))
#'user/a
user=> (dotimes [_ 10] (send a inc))
nil
;; ...later...
user=> @a
10

Еще один вариант: pcalls и pmap. Там также future. Все они описаны в Clojure API.

Ответ 2

Обычно, когда я хочу начать поток в Clojure, я просто использую future.

Помимо простого использования, это имеет то преимущество, что вам не нужно делать какой-либо беспорядочный Java-взаимодействие для доступа к базовым механизмам потоков Java.

Использование примера:

(future (some-long-running-function))

Это приведет к асинхронному выполнению функции в другом потоке.

(def a (future (* 10 10)))

Если вы хотите получить результат, просто разыщите будущее, например:

@a
=> 100

Обратите внимание, что @a будет блокироваться, пока будущий поток не завершит свою работу.

Ответ 3

Программирование Clojure не затрагивает этот вопрос до тех пор, пока страница 167: "Использовать агенты для асинхронных обновлений".

Перед тем, как начинать темы, обратите внимание, что Clojure будет многозадачно, учитывая половину шанса. Я написал программы, беспечно не знающие concurrency, и обнаружил, что, когда условия правильные, они занимают более одного процессора. Я знаю, что это не очень строгое определение: я еще не исследовал это подробно.

Но для тех случаев, когда вам действительно нужна отдельная отдельная деятельность, один из ответов Clojure, по-видимому, является агентом.

(agent initial-state)

создаст его. Это не похоже на Java-поток, поскольку он является блоком кода, ожидающим исполнения. Вместо этого это действие, ожидающее выполнения работы. Вы делаете это через

(send agent update-fn & args)

Пример

(def counter (agent 0))

counter - ваше имя и дескриптор для агента; состояние агента - это число 0.

Установив это, вы можете отправить работу агенту:

(send counter inc)

скажет, чтобы применить данную функцию к ее состоянию.

Вы можете позже вывести состояние из агента, разыгрывая его:

@counter даст вам текущее значение числа, которое начиналось с 0.

Функция await позволит вам сделать что-то вроде join для активности агента, если она длинная:

(await & agents) будет ждать, пока они все закончится; там также другая версия, которая занимает тайм-аут.

Ответ 4

Да, способ, которым вы запускаете Java-поток в Clojure, похож на то, что у вас есть.

Однако, реальный вопрос: зачем вам это делать? Clojure имеет гораздо лучшие конструкторы concurrency, чем потоки.

Если вы посмотрите на канонический пример concurrency в Clojure, Rich Hickey ant colony simulation, вы увидите, что использует ровно 0 потоков. Единственная ссылка на java.lang.Thread во всем источнике - это три вызова Thread.sleep, единственная цель которых - замедлить симуляцию вниз, чтобы вы действительно могли видеть, что происходит в пользовательском интерфейсе.

Вся логика выполняется в Агенты: один агент для каждого ant, один агент для анимации и один агент для испарения феромонов. Игровое поле является транзакционным реф. Не поток, ни замок в поле зрения.

Ответ 5

Использование будущего - это, как правило, самый простой доступ к потоковому доступу. В зависимости от того, что вы хотите сделать:)

Ответ 6

Просто чтобы добавить мои два цента (через 7 лет): Clojure функции реализуют IFn интерфейс, который расширяет Callable как а также Runnable. Следовательно, вы можете просто передать их таким классам, как Thread.

Если ваш проект может уже использовать core.async, я предпочитаю использовать макрос go:

(go func)

Выполняет func в суперлегком поток IOC (инверсия управления):

go [...] превратит тело в конечный автомат. По достижении какой-либо операции блокировки конечный автомат будет "припаркован", и фактический поток управления будет освобожден. [...] Когда операция блокировки завершена, код будет возобновлен [...]

В случае, если func собирается выполнять I/O или какую-то длительную задачу, вы должны использовать Thread, который также является частью core.async(проверьте этот отличный пост в блоге):

(thread func)

В любом случае, если вы хотите придерживаться синтаксиса Java interop, рассмотрите макрос -> (поток/стрелка):

(-> (Thread. func) .start)

Ответ 7

Маска (future f) обертывает форму f в Callable (через fn *) и немедленно отправляет ее в пул потоков.

если вам нужна ссылка на объект java.lang.Thread, например, чтобы использовать его как java.lang.Runtime shutdown hook, вы можете создать Thread следующим образом:

(proxy [Thread] [] (run [] (println "running")))

Это еще не запустит поток, создайте его. Чтобы создать и запустить поток, отправьте его в пул потоков или вызовите .start на нем:

(->
 (proxy [Thread] [] (run [] (println "running")))
 (.start))

Ответы Brians также создают поток, но не нуждаются в прокси, так что очень элегантно. С другой стороны, используя прокси-сервер, мы можем избежать создания Callable.