Спящая нить внутри ExecutorService (Java/Clojure)
У меня довольно большое количество потоков, создаваемых внутри программы clojure:
(import '(java.util.concurrent Executors))
(def *pool*
(Executors/newCachedThreadPool))
(defn do-something []
; work
Thread/sleep 200
; repeat)
(dotimes [i 10000]
(.submit *pool* do-something))
Для меня это было некоторое время между JVM, и я в основном задаюсь вопросом: есть ли аргументы против использования sleep или yield внутри функции, выполняемой Исполнителем? Если я правильно понимаю, в этом случае у каждого из моих работников есть свой собственный поток, и поэтому не должно быть никаких побочных эффектов.
Если Исполнитель использует FixedThreadPool:
(Executors/newFixedThreadPool 1000)
Все усложняется тем, что потоки не будут возвращаться в пул до тех пор, пока их работа не будет завершена, а это означает, что другим работникам, находящимся в очереди, потребуется больше времени для завершения, если потоки спят.
Правильно ли мое понимание потоков в этом случае?
(Примечание: я подозреваю, что мой дизайн на самом деле неправильный, но я просто хочу убедиться, что я на правильной странице)
2 ответа
Исполнитель - это концептуально очередь задач + рабочий пул. Ваше объяснение того, что здесь произойдет, в основном верно. Когда вы отправляете задачу исполнителю, работа ставится в очередь до тех пор, пока поток не сможет выполнить задачу. Когда она выполняет задачу, эта задача владеет потоком, и спящий режим заблокирует выполнение других задач в этом рабочем потоке.
В зависимости от того, что вы делаете, это может быть хорошо (хотя это необычно и, вероятно, плохо спать внутри задачи). Чаще всего блокируют поток как побочный эффект ожидания ввода-вывода (например, блокируется при вызове сокета или db).
Как правило, если вы выполняете периодическую работу, лучше обрабатывать ее вне пула и запускать задачи, когда они должны быть выполнены, или еще лучше, использовать ScheduledExecutorService вместо Executors/newScheduledThreadPool.
Другим основным механизмом в Java для выполнения задач, основанных на времени, является java.util.Timer, который немного проще в использовании, но не такой надежный, как ScheduledExecutorService.
Другой альтернативой из Clojure является явное помещение рабочего в фоновый поток, управляемый Clojure, а не вами:
(defn do-task []
(println (java.util.Date.) "doing task"))
(defn worker [f n wait]
(doseq [task (repeat n f)]
(f)
(Thread/sleep wait)))
;; use future to execute worker in a background thread managed by Clojure
(future (worker do-task 10 1000))
;; the call to future returns immediately but in the background console
;; you will see the tasks being run.
Альтернатива для сна ваших потоков состоит в том, чтобы у каждого работника было длинное значение sleepUntil. Когда ваш исполнитель вызывает работника, если он спит, он немедленно возвращается. В противном случае он выполняет свою работу, а затем возвращается. Это может помочь уменьшить счет вашего потока, потому что FixedThreadPoolExecutor сможет обрабатывать гораздо больше рабочих, чем имеет потоки, если большинство из них помечены как спящие и быстро возвращаются.