Почему отмененные фьючерсы Clojure продолжают использовать CPU? - программирование
Подтвердить что ты не робот

Почему отмененные фьючерсы Clojure продолжают использовать CPU?

У меня есть много примеров байт-кода Java, все из которых я хотел бы выполнить из Clojure. Каждая последовательность байт-кода может содержать бесконечный цикл, и в этом случае я бы хотел остановить его через пару секунд. Я рассматривал будущее как средство для этого. Охвативсь за пару реализаций, я пробовал и этот код:

(deref (future (loop[a 1] (recur a)) :done!) 1000 :impatient!)

... а также код https://gist.github.com/3124000

В обоих случаях цикл, как представляется, истекает соответствующим образом (и в последнем случае, как сообщается, будущее было сделано и отменено), но я вижу, что мой рост использования ЦП достигает 99% или около того и остается там. Я также вижу, что каждый раз, когда я запускаю этот код, мой Java-процесс получает дополнительный поток.

Мне кажется, что будущее отменяется, но код все еще работает. В моей программе мне нужно будет запустить и тайм-аут несколько очень плотных бесконечных циклов (например, эквивалент байт-кода Java "20 PRINT GOTO 10" ), и у меня нет возможности модифицировать код, который я запускаю.

Любые идеи, почему я вижу это поведение; что я мог сделать, чтобы предотвратить это; или альтернативные методы для реализации моей цели запуска и выключения такого кода?

4b9b3361

Ответ 1

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

У меня была функция clojail для этого. Не стесняйтесь вырывать функцию или просто вставлять библиотеку.

Ответ 2

Поддерживаемый Java механизм отмены потока прерывает. Метод .stop() устарел по какой-либо причине - см. документы, эффективная Java, Java Concurrency на практике и т.д.

Фактически реализация FutureTask (которая поддерживает future-cancel) основана на прерываниях.

На сегодняшний день каждое будущее Clojure поддерживается неограниченным пулом потоков (подобно действиям send-off). Таким образом, вы можете использовать примитивы прерывания потока:

(def worker
  (let [p (promise)]
    {:future (future
               (let [t (Thread/currentThread)]
                 (deliver p t)
                 (while (not (Thread/interrupted))
                   (println 42)
                   (Thread/sleep 400))))
     :thread @p}))

Теперь можно успешно выполнить либо (.interrupt (:thread worker)), либо (future-cancel (:future worker)).

Хотя это связывает механизм отмены потоков с функциями Futures, доступные реализации Executor достаточно умны, чтобы очистить прерванный статус, который могла быть установлена ​​предыдущей задачей, поэтому перерывы в одной задаче никогда не влияют на другие задачи - даже если они запускается в том же потоке.

Дело в том, что упреждающее уничтожение потоков - это, как правило, плохая идея - если вы хотите отменить задачи, вы должны сделать их явно отменяемыми, тем или иным способом. OTOH, в вашем конкретном случае вы выполняете непрозрачный байт-код, поэтому нет больше возможностей, чем прибегать к .stop().

Ответ 3

В соответствии с документом Java API cancel не предоставляется возможность немедленно остановить задачу.

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