Java/GPars - мой пул потоков, кажется, "забит"
Что я делаю: я просматриваю таблицу компаний в базе данных... у каждой компании есть текст description
поле, и внутри этого поля может быть несколько гиперссылок (редко больше 4). Я хочу протестировать эти ссылки, используя curl
для "плохого" ответа (обычно 404, но все, что не 200, будет представлять интерес).
Между прочим, это применимо к Java так же, как Groovy, без сомнения, и людям любого убеждения может быть интересно узнать, что базовый класс пула потоков, используемый здесь GPars (параллелизм Groovy) ForkJoinPool
,
Собрав эти URL с помощью Matcher
с использованием Pattern
/(https?:.*?)\)/
Я получаю карту descripURLs
"URL" -> "название компании". Тогда я использую withPool
с большой пропускной способностью (из-за внутренней задержки ожидания ответов, очевидно), например, так:
startMillis = System.currentTimeMillis()
AtomicInteger nRequest = new AtomicInteger()
AtomicInteger nResponsesReceived = new AtomicInteger()
poolObject = null
resultP = withPool( 50 ){ pool ->
poolObject = pool
descripURLs.eachParallel{ url, name ->
int localNRequest = nRequest.incrementAndGet()
Process process = checkURL( url )
def response
try {
//// with the next line TIME PASSES in this Thread...
response = process.text
} catch( Exception e ) {
System.err.println "$e"
}
// NB this line doesn't appear to make much difference
process.destroyForcibly()
nResponses = nResponsesReceived.incrementAndGet()
int nRequestsNowMade = nRequest.get()
if( response.trim() != '200' ) {
println "\n*** request $localNRequest BAD RESPONSE\nname $name url $url\nresponse |$response|" +
"\n$nRequestsNowMade made, outstanding ${nRequestsNowMade - nResponses}"
// NB following line may of course not be printed immmediately after the above line, due to parallelism
println "\nprocess poolSize $pool.poolSize, queuedTaskCount $pool.queuedTaskCount," +
" queuedSubmissionCount? $pool.queuedSubmissionCount"
}
println "time now ${System.currentTimeMillis() - startMillis}, activeThreadCount $pool.activeThreadCount"
}
println "END OF withPool iterations"
println "pool $pool class ${pool.class.simpleName}, activeThreadCount $pool.activeThreadCount"
pool.shutdownNow()
}
println "resultP $resultP class ${resultP.class.simpleName}"
println "pool $poolObject class ${poolObject.class.simpleName}"
println "pool shutdown? $poolObject.shutdown"
def checkURL( url ) {
def process = "curl -LI $url -o /dev/null -w '%{http_code}\n' -s".execute()
// this appears necessary... otherwise potentially you can have processes hanging around forever
process.waitForOrKill( 8000 ) // 8 s to get a reponse
process.addShutdownHook{
println "shutdown on url $url"
}
process
}
То, что я наблюдаю с пулом из 50 потоков, как указано выше, заключается в том, что для 500 URL потребуется 20 с. Я экспериментировал с меньшими и большими пулами, и 100, кажется, ничего не меняет, но 25 кажется медленнее, а 10 больше, чем 40 с. Времена также замечательно согласуются от запуска к запуску для того же размера пула.
Что я не понимаю, так это то, что Process
Крюки отключения ES работают только в самом конце закрытия... для всех 500 Process
эс! Это не означает, что на компьютере висит 500 реальных процессов: с помощью диспетчера задач я вижу, что число curl.exe
Процессы в любой момент времени относительно невелики.
В то же время я наблюдаю от println
Здесь мы видим, что число активных потоков здесь начинается с 50, но затем уменьшается на протяжении всего цикла, достигая 3 (обычно) к концу. И еще... Я также могу заметить, что окончательные запросы добавляются только в самом конце цикла.
Это заставляет меня задуматься о том, не является ли пул потоков каким-то образом "забитым" этим "незаконченным бизнесом" этих "зомби" Process
es... Я ожидаю, что окончательные запросы (из 500 сделанных) будут сделаны задолго до конца пробега. Есть ли способ, которым я могу закрыть эти Process
раньше?
1 ответ
Ни Java, ни Groovy не поддерживают метод addShutdownHook
на ребенка Process
экземпляров.
Единственный метод addShutdownHook
что Java поддерживает на Runtime
пример. Это добавляет ловушку для запуска при выключении JVM.
Groovy добавляет удобство addShutdownHook()
к Object
класс, так что вам не нужно писать Runtime.getRuntime().addShutdownHook(..)
, но это ничего не меняет в базовом механизме: эти ловушки выполняются только при завершении работы JVM.
Потому что замыкания, которые вы добавляете с process.addShutdownHook
Скорее всего, сохранить ссылки на process
Например, они будут сохраняться до завершения работы JVM (объекты Java, но не процессы ОС)